From 1fa1d69bd810b1cb6e230e5c0dfb8e66bb66b872 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Mon, 18 Dec 2023 10:29:31 +0700 Subject: [PATCH 01/25] Support for datetime calculations in cohort definitions --- .../circe/check/checkers/Comparisons.java | 28 +- .../checkers/ExitCriteriaDaysOffsetCheck.java | 6 +- .../checkers/FirstTimeInHistoryCheck.java | 4 +- .../circe/check/checkers/RangeCheck.java | 9 +- .../check/checkers/TimePatternCheck.java | 4 +- .../CohortExpressionQueryBuilder.java | 23 +- .../cohortdefinition/CollapseSettings.java | 15 +- .../cohortdefinition/DateOffsetStrategy.java | 10 +- .../circe/cohortdefinition/IntervalUnit.java | 20 + .../cohortdefinition/ObservationFilter.java | 2 +- .../ohdsi/circe/cohortdefinition/Window.java | 9 +- .../ConditionOccurrenceSqlBuilder.java | 8 +- .../printfriendly/cohortExpression.ftl | 2 +- .../printfriendly/conceptSet.ftl | 22 +- .../printfriendly/criteriaTypes.ftl | 4 +- .../printfriendly/endStrategyTypes.ftl | 2 +- .../printfriendly/inputTypes.ftl | 10 +- .../sql/dateOffsetStrategy.sql | 7 +- .../cohortdefinition/sql/generateCohort.sql | 6 +- .../circe/check/checkers/ComparisonsTest.java | 22 +- .../builders/CriteriaUtils.java | 4 +- .../additionalCriteriaCheckValueCorrect.json | 3 +- ...additionalCriteriaCheckValueIncorrect.json | 9 +- .../censoringEventCheckValueCorrect.json | 3 +- .../censoringEventCheckValueIncorrect.json | 3 +- .../checkers/childGroupExpression.json | 3 +- .../conceptSetCriteriaCheckCorrect.json | 3 +- .../conceptSetCriteriaCheckIncorrect.json | 3 +- .../contradictionsCriteriaCheckCorrect.json | 3 +- .../contradictionsCriteriaCheckIncorrect.json | 3 +- .../checkers/deathTimeWindowCheckCorrect.json | 12 +- .../deathTimeWindowCheckIncorrect.json | 12 +- .../checkers/domainTypeCheckCorrect.json | 3 +- .../checkers/domainTypeCheckIncorrect.json | 3 +- .../checkers/drugDomainCheckCorrect.json | 3 +- .../checkers/drugDomainCheckIncorrect.json | 3 +- .../checkers/drugEraCheckCorrect.json | 9 +- .../checkers/drugEraCheckIncorrect.json | 3 +- .../duplicatesConceptSetCheckCorrect.json | 3 +- .../duplicatesConceptSetCheckIncorrect.json | 3 +- .../duplicatesCriteriaCheckCorrect.json | 3 +- .../duplicatesCriteriaCheckIncorrect.json | 3 +- .../checkers/emptyCensoringCriteriaList.json | 3 +- .../checkers/emptyCorrelatedCriteria.json | 3 +- .../emptyDemographicCheckCorrect.json | 3 +- .../emptyDemographicCheckIncorrect.json | 3 +- .../checkers/emptyInclusionRules.json | 3 +- .../checkers/emptyPrimaryCriteriaList.json | 3 +- .../eventsProgressionCheckCorrect.json | 3 +- .../eventsProgressionCheckIncorrect.json | 3 +- .../inclusionRulesCheckValueCorrect.json | 3 +- .../inclusionRulesCheckValueIncorrect.json | 3 +- .../checkers/noExitCriteriaCheck.json | 3 +- .../noExitCriteriaCheckEarliestEvent.json | 3 +- .../primaryCriteriaCheckValueCorrect.json | 3 +- .../primaryCriteriaCheckValueIncorrect.json | 3 +- .../checkers/timePatternCheckCorrect.json | 12 +- .../checkers/timePatternCheckIncorrect.json | 21 +- .../resources/checkers/unusedConceptSet.json | 3 +- .../checkers/unusedConceptSetCorrect.json | 3 +- .../allCriteria/allCriteriaExpression.json | 93 ++-- .../censorWindow/censorWindowExpression.json | 6 +- .../censorWindow/censorWindow_PREP.json | 3 + .../correlatedCriteria/countsExpression.json | 6 +- .../correlatedCriteria/countsTest_PREP.json | 2 + .../correlatedCriteria/groupExpression.json | 27 +- .../correlatedCriteria/groupTest_PREP.json | 3 + .../correlatedCriteria/visitExpression.json | 9 +- .../eraDupes/eraDupesExpression.json | 6 +- .../eraDupes/eraDupes_PREP.json | 10 + .../eraDupes/eraDupes_VERIFY.json | 12 + .../exits/censorEventExpression.json | 6 +- .../continuousExposureCensorExpression.json | 3 +- .../exits/continuousExposureExpression.json | 3 +- .../exits/fixedOffsetCensorExpression.json | 6 +- .../exits/fixedOffsetExpression.json | 6 +- .../firstOccurrenceTestExpression.json | 3 +- .../firstOccurrenceTest_PREP.json | 3 + .../inclusionRules/simpleInclusionRule.json | 6 +- .../simpleInclusionRule_PREP.json | 3 + .../limits/limitExpression.json | 9 +- .../limits/limitTest_PREP.json | 7 + .../limits/limitTest_VERIFY.json | 48 +- .../mixedConceptsetsExpression.json | 3 +- .../mixedConceptsets_PREP.json | 2 + .../distinctDefault_PREP.json | 103 ++-- .../corelatedcriteria/distinctStart_PREP.json | 103 ++-- .../corelatedcriteria/distinctVisit_PREP.json | 5 + .../simpleInclusion_PREP.json | 5 + .../conditionOccurrenceDateOffset_PREP.json | 6 + src/test/resources/ddl/cdm_v5.0.sql | 515 +++++++++--------- .../firstOccurrenceTest_PREP.json | 2 + .../printfriendly/allAttributes.json | 48 +- .../resources/printfriendly/anyCondition.json | 3 +- .../printfriendly/censorCriteria.json | 9 +- .../printfriendly/conceptSet_simple.json | 3 +- .../resources/printfriendly/conditionEra.json | 21 +- .../printfriendly/conditionOccurrence.json | 12 +- .../continuousObservation_none.json | 3 +- .../continuousObservation_post.json | 3 +- .../continuousObservation_prior.json | 3 +- .../continuousObservation_priorpost.json | 3 +- .../printfriendly/countCriteria.json | 39 +- .../printfriendly/countDistinctCriteria.json | 18 +- .../printfriendly/customEraExit.json | 3 +- .../resources/printfriendly/dateOffset.json | 6 +- src/test/resources/printfriendly/death.json | 6 +- .../printfriendly/deviceExposure.json | 15 +- src/test/resources/printfriendly/doseEra.json | 33 +- src/test/resources/printfriendly/drugEra.json | 9 +- .../resources/printfriendly/drugExposure.json | 12 +- .../resources/printfriendly/measurement.json | 9 +- .../printfriendly/noCensorCriteria.json | 7 +- .../printfriendly/nullCodesetId.json | 3 +- .../resources/printfriendly/observation.json | 6 +- .../printfriendly/observationPeriod_1.json | 6 +- .../printfriendly/procedureOccurrence.json | 6 +- .../resources/printfriendly/specimen.json | 6 +- src/test/resources/printfriendly/visit.json | 6 +- .../resources/printfriendly/visitDetail.json | 6 +- .../windowConditionOccurrence_PREP.json | 5 + 121 files changed, 1033 insertions(+), 691 deletions(-) create mode 100644 src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index bf2404e3..82ffe189 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -103,18 +103,30 @@ public static Predicate compare(Concept source) { } public static int compareTo(ObservationFilter filter, Window window) { + int range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + int range2Start = getTimeInSeconds(window.start); + int range2End = getTimeInSeconds(window.end); + return range1 - (range2End - range2Start); + } - int range1 = filter.postDays + filter.priorDays; - int range2Start = 0, range2End = 0; - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days)) { - range2Start = window.start.coeff * window.start.days; - } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days)) { - range2End = window.end.coeff * window.end.days; + private static int getTimeInSeconds(Window.Endpoint endpoint) { + if (Objects.isNull(endpoint)) { + return 0; } - return range1 - (range2End - range2Start); + int convertRate; + if (endpoint.timeUnit.equals(IntervalUnit.DAY.getName())) { + convertRate = 24 * 60 * 60; + } else if (endpoint.timeUnit.equals(IntervalUnit.HOUR.getName())) { + convertRate = 60 * 60; + } else if (endpoint.timeUnit.equals(IntervalUnit.MINUTE.getName())) { + convertRate = 60; + } else convertRate = 1; + return Objects.nonNull(endpoint.timeUnitValue) + ? endpoint.coeff * endpoint.timeUnitValue * convertRate + : 0; } + public static boolean compare(Criteria c1, Criteria c2) { boolean result = false; diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index b8e83804..1264be55 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -28,7 +28,7 @@ public class ExitCriteriaDaysOffsetCheck extends BaseCheck { - private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: Days offset from start date should be greater than 0"; + private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: %ss offset from start date should be greater than 0"; @Override protected WarningSeverity defineSeverity() { @@ -42,7 +42,7 @@ protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) .isA(DateOffsetStrategy.class) .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offset) - .then(() -> reporter.add(DAYS_OFFSET_WARNING))); + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 8280bcd7..95f2109f 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -43,8 +43,8 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.days != null) || (c.startWindow.end != null - && c.startWindow.end.days != null))) + && c.startWindow.start.timeUnitValue != null) || (c.startWindow.end != null + && c.startWindow.end.timeUnitValue != null))) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index e3b2a8eb..6b927308 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -54,17 +54,16 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep private void checkWindow(Window window, WarningReporter reporter, String name) { if (Objects.nonNull(window)) { - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days) && window.start.days < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.days, "start"); + if (Objects.nonNull(window.start) && Objects.nonNull(window.start.timeUnitValue) && window.start.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.timeUnitValue, "start"); } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days) && window.end.days < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.days, "end"); + if (Objects.nonNull(window.end) && Objects.nonNull(window.end.timeUnitValue) && window.end.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.timeUnitValue, "end"); } } } private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { - if (Objects.nonNull(filter)) { if (filter.priorDays < 0) { reporter.add(NEGATIVE_VALUE_ERROR, name, filter.priorDays, "prior days"); diff --git a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java index a01c75f6..2a3c5407 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java @@ -83,7 +83,7 @@ private String formatTimeWindow(TimeWindowInfo ti) { } private String formatDays(Window.Endpoint endpoint) { - return Objects.nonNull(endpoint.days) ? String.valueOf(endpoint.days) : "all"; + return Objects.nonNull(endpoint.timeUnitValue) ? String.valueOf(endpoint.timeUnitValue) : "all"; } private String formatCoeff(Window.Endpoint endpoint) { @@ -92,7 +92,7 @@ private String formatCoeff(Window.Endpoint endpoint) { private Integer startDays(Window window) { return Objects.nonNull(window) && Objects.nonNull(window.start) ? - (Objects.nonNull(window.start.days) ? window.start.days : 0) * window.start.coeff : 0; + (Objects.nonNull(window.start.timeUnitValue) ? window.start.timeUnitValue : 0) * window.start.coeff : 0; } class TimeWindowInfo { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index bf65f8ab..50071030 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -351,7 +351,9 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); +// resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -546,8 +548,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.days != null) { - startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); + if (startWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); } else { startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -556,8 +558,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", startEventDateExpression, startExpression)); } - if (startWindow.end.days != null) { - endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); + if (startWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); } else { endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -573,8 +575,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit String endIndexDateExpression = (endWindow.useIndexEnd != null && endWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; // for backwards compatability, having a null endWindow.useIndexEnd means they SHOULD use the index end date. String endEventDateExpression = (endWindow.useEventEnd == null || endWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (endWindow.start.days != null) { - startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); + if (endWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); } else { startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -583,8 +585,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); } - if (endWindow.end.days != null) { - endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); + if (endWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); } else { endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -763,7 +765,8 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); return strategySql; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 39db0e79..0850fb71 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -21,11 +21,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class CollapseSettings { - - @JsonProperty("CollapseType") - public CollapseType collapseType = CollapseType.ERA; - - @JsonProperty("EraPad") - public int eraPad = 0; - + + @JsonProperty("CollapseType") + public CollapseType collapseType = CollapseType.ERA; + @JsonProperty("EraPadUnit") + public String eraPadUnit = IntervalUnit.DAY.getName(); + @JsonProperty("EraPadUnitValue") + public int eraPadUnitValue = 0; + } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 0ed6b8a8..8f610267 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -30,13 +30,15 @@ public enum DateField { StartDate, EndDate } - + @JsonProperty("DateField") public DateField dateField = DateField.StartDate; - @JsonProperty("Offset") - public int offset = 0; - + @JsonProperty("OffsetUnitValue") + public int offsetUnitValue = 0; + @JsonProperty("OffsetUnit") + public String offsetUnit = IntervalUnit.DAY.getName(); + @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { return dispatcher.getStrategySql(this, eventTable); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java new file mode 100644 index 00000000..e55b58e7 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -0,0 +1,20 @@ +package org.ohdsi.circe.cohortdefinition; + +import jdk.nashorn.internal.objects.annotations.Getter; + +public enum IntervalUnit { + DAY("day"), + HOUR("hour"), + MINUTE("minute"), + SECOND("second"); + + private final String name; + + IntervalUnit(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java b/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java index 8be10161..53e514bb 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java @@ -27,6 +27,6 @@ public class ObservationFilter { @JsonProperty("PriorDays") public int priorDays; - @JsonProperty("PostDays") + @JsonProperty("PostDays") public int postDays; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index c1a2563b..e746eb50 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -27,12 +27,13 @@ public class Window { public static class Endpoint { - @JsonProperty("Days") - public Integer days; - + @JsonProperty("TimeUnitValue") + public Integer timeUnitValue; + @JsonProperty("TimeUnit") + public String timeUnit = IntervalUnit.DAY.getName(); @JsonProperty("Coeff") public int coeff; - } + } @JsonProperty("Start") public Endpoint start; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 92880b74..35defc8a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -69,7 +69,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w // first if (criteria.first != null && criteria.first == true) { whereClauses.add("C.ordinal = 1"); - query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_date, co.condition_occurrence_id) as ordinal"); + query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_datetime, co.condition_occurrence_id) as ordinal"); } else { query = StringUtils.replace(query, "@ordinalExpression", ""); } @@ -99,10 +99,10 @@ protected List resolveSelectClauses(T criteria) { // dateAdjustment or default start/end dates if (criteria.dateAdjustment != null) { selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, - criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", - criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); + criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))", + criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))")); } else { - selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + selectCols.add("co.condition_start_datetime as start_date, COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime)) as end_date"); } return selectCols; } diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl index 8d685318..c79088f3 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl @@ -64,7 +64,7 @@ ${cc?counter}. <@ct.Criteria c=cc/> ### Cohort Eras -Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPad} days of each other. +Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPadUnitValue} ${collapseSettings.eraPadUnit}s of each other. <#-- main template: end --> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl index 54a65012..05e69847 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl @@ -1,22 +1,22 @@ <#import "./utils.ftl" as utils> <#list conceptSets as conceptSet> -### ${conceptSet.name} -<#list conceptSet.expression.items> -|Concept ID|Concept Name|Code|Vocabulary|Excluded|Descendants|Mapped -|:---|:---------------------------------------|:--|:-----|:--:|:--:|:--:| -<#items as conceptSetItem> -|${conceptSetItem.concept.conceptId?c}|<#-- + ### ${conceptSet.name} + <#list conceptSet.expression.items> + |Concept ID|Concept Name|Code|Vocabulary|Excluded|Descendants|Mapped + |:---|:---------------------------------------|:--|:-----|:--:|:--:|:--:| + <#items as conceptSetItem> + |${conceptSetItem.concept.conceptId?c}|<#-- -->${conceptSetItem.concept.conceptName}|<#-- -->${conceptSetItem.concept.conceptCode}|<#-- -->${conceptSetItem.concept.vocabularyId}|<#-- -->${utils.renderCheckbox(conceptSetItem.isExcluded)}|<#-- -->${utils.renderCheckbox(conceptSetItem.includeDescendants)}|<#-- -->${utils.renderCheckbox(conceptSetItem.includeMapped)}| - - + -<#else> -There are no concept set items in this concept set. - + <#else> + There are no concept set items in this concept set. + + diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index ab1392a3..3dc2ee4a 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -280,8 +280,8 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> <#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 8f021d3d..a71c1bf4 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -24,7 +24,7 @@ The person exits the cohort at the end of continuous observation. <#assign dateOffsetFieldOptions = [{"id": "StartDate", "name": "start date"}, {"id": "EndDate", "name": "end date"}]> <#macro DateOffsetStrategy s> -The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offset "day"/>. +The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. <#macro CustomEraStrategy s> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 00928c04..d26b4cf1 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -90,11 +90,11 @@ END Note!!!! <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> <#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- ---><#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.days??>in the ${w.start.days} days<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +--><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} <#-- User Defined Period --> diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index 16a7e395..b79289dd 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,6 +1,9 @@ -- date offset strategy -select event_id, person_id, - case when DATEADD(day,@offset,@dateField) > op_end_date then op_end_date else DATEADD(day,@offset,@dateField) end as end_date +select event_id, + person_id, + case + when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date + else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date INTO #strategy_ends from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 436f37bd..0a873c67 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -2,7 +2,7 @@ SELECT event_id, person_id, start_date, end_date, op_start_date, op_end_date, visit_occurrence_id INTO #qualified_events -FROM +FROM ( select pe.event_id, pe.person_id, pe.start_date, pe.end_date, pe.op_start_date, pe.op_end_date, row_number() over (partition by pe.person_id order by pe.start_date @QualifiedEventSort) as ordinal, cast(pe.visit_occurrence_id as bigint) as visit_occurrence_id FROM (@primaryEventsQuery) pe @@ -63,7 +63,7 @@ from ( --cteEnds JOIN ( -- cteEndDates SELECT person_id - , DATEADD(day,-1 * @eraconstructorpad, event_date) as end_date + , DATEADD(@era_pad_unit, -1 * @era_pad, event_date) as end_date FROM ( SELECT @@ -84,7 +84,7 @@ from ( --cteEnds SELECT person_id - , DATEADD(day,@eraconstructorpad,end_date) as end_date + , DATEADD(@era_pad_unit, @era_pad, end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index a9edf413..2f4fc4c5 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -6,11 +6,7 @@ import org.ohdsi.circe.vocabulary.Concept; import org.ohdsi.circe.vocabulary.ConceptSetExpression; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.time.LocalDate; -import java.util.Objects; import java.util.function.Predicate; import static java.time.temporal.ChronoUnit.DAYS; @@ -117,19 +113,23 @@ public void compareTo() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.days = 10; + window.start.timeUnitValue = 10; + window.start.timeUnit = IntervalUnit.DAY.getName(); window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.days = 5; + window.end.timeUnitValue = 5; + window.end.timeUnit = IntervalUnit.DAY.getName(); window.end.coeff = 1; assertEquals(0, Comparisons.compareTo(filter, window)); - window.start.days = 5; - window.end.days = 5; + window.start.timeUnitValue = 5; + window.start.timeUnit = IntervalUnit.DAY.getName(); + window.end.timeUnitValue = 5; + window.end.timeUnit = IntervalUnit.DAY.getName(); - assertEquals(5, Comparisons.compareTo(filter, window)); + assertEquals(432000, Comparisons.compareTo(filter, window)); } @Test @@ -243,10 +243,10 @@ public void compareCriteria() { public void isBefore() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.days = 1; + window.start.timeUnitValue = 1; window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.days = 1; + window.end.timeUnitValue = 1; window.end.coeff = 1; assertEquals(false, Comparisons.isBefore(window)); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index 40481a30..c20ca187 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -29,12 +29,12 @@ public static Window getPrior365Window() { // index starts between 365d before Window.Endpoint startPoint = new Window.Endpoint(); startPoint.coeff = -1; - startPoint.days = 365; + startPoint.timeUnitValue = 365; prior365Window.start = startPoint; // ... and 0 days before Window.Endpoint endPoint = new Window.Endpoint(); endPoint.coeff = -1; - endPoint.days = 0; + endPoint.timeUnitValue = 0; prior365Window.end = endPoint; return prior365Window; } diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json index 9204b353..13b41373 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json @@ -539,7 +539,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json index 5da3b99f..be6f11f6 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json @@ -258,11 +258,13 @@ }, "StartWindow": { "Start": { - "Days": -3, + "TimeUnitValue": -3, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": -2, + "TimeUnitValue": -2, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -307,7 +309,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueCorrect.json b/src/test/resources/checkers/censoringEventCheckValueCorrect.json index 7df72672..8b517a9e 100644 --- a/src/test/resources/checkers/censoringEventCheckValueCorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueCorrect.json @@ -408,7 +408,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json index 515aca1b..e791905b 100644 --- a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json @@ -288,7 +288,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/childGroupExpression.json b/src/test/resources/checkers/childGroupExpression.json index 01ed8301..6d040339 100644 --- a/src/test/resources/checkers/childGroupExpression.json +++ b/src/test/resources/checkers/childGroupExpression.json @@ -189,7 +189,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json index 34afceb4..9c9c05e3 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json @@ -115,7 +115,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json index e0d400d5..af967543 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json @@ -89,7 +89,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json index 7f224759..e64e1c55 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json @@ -116,7 +116,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json index 3153dd0c..92b71212 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json @@ -116,7 +116,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json index 16f05ebc..f51ad3d1 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json @@ -39,7 +39,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -83,7 +84,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -104,7 +106,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -131,7 +134,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json index ad419909..2b593bac 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json @@ -39,7 +39,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -83,7 +84,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -104,7 +106,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -131,7 +134,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckCorrect.json b/src/test/resources/checkers/domainTypeCheckCorrect.json index dc401077..678b7562 100644 --- a/src/test/resources/checkers/domainTypeCheckCorrect.json +++ b/src/test/resources/checkers/domainTypeCheckCorrect.json @@ -217,7 +217,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckIncorrect.json b/src/test/resources/checkers/domainTypeCheckIncorrect.json index 34afceb4..9c9c05e3 100644 --- a/src/test/resources/checkers/domainTypeCheckIncorrect.json +++ b/src/test/resources/checkers/domainTypeCheckIncorrect.json @@ -115,7 +115,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckCorrect.json b/src/test/resources/checkers/drugDomainCheckCorrect.json index 42e15e84..0a070252 100644 --- a/src/test/resources/checkers/drugDomainCheckCorrect.json +++ b/src/test/resources/checkers/drugDomainCheckCorrect.json @@ -168,7 +168,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckIncorrect.json b/src/test/resources/checkers/drugDomainCheckIncorrect.json index 4b7364a6..64a99002 100644 --- a/src/test/resources/checkers/drugDomainCheckIncorrect.json +++ b/src/test/resources/checkers/drugDomainCheckIncorrect.json @@ -168,7 +168,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugEraCheckCorrect.json b/src/test/resources/checkers/drugEraCheckCorrect.json index 047e2f50..565aad14 100644 --- a/src/test/resources/checkers/drugEraCheckCorrect.json +++ b/src/test/resources/checkers/drugEraCheckCorrect.json @@ -53,11 +53,13 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": true @@ -76,7 +78,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugEraCheckIncorrect.json b/src/test/resources/checkers/drugEraCheckIncorrect.json index 2fdbba89..22ef9d11 100644 --- a/src/test/resources/checkers/drugEraCheckIncorrect.json +++ b/src/test/resources/checkers/drugEraCheckIncorrect.json @@ -71,7 +71,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json index 4ad7d1c3..4b4cdbe3 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json @@ -50,7 +50,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json index da030ecf..1741beb8 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json @@ -73,7 +73,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json index 6c79f167..b2d56fae 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json @@ -185,7 +185,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json index e726c78c..05b5b93e 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json @@ -532,7 +532,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/emptyCensoringCriteriaList.json b/src/test/resources/checkers/emptyCensoringCriteriaList.json index ff98150d..6dec6bdc 100644 --- a/src/test/resources/checkers/emptyCensoringCriteriaList.json +++ b/src/test/resources/checkers/emptyCensoringCriteriaList.json @@ -49,7 +49,8 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyCorrelatedCriteria.json b/src/test/resources/checkers/emptyCorrelatedCriteria.json index 2b70c327..dbe91807 100644 --- a/src/test/resources/checkers/emptyCorrelatedCriteria.json +++ b/src/test/resources/checkers/emptyCorrelatedCriteria.json @@ -148,7 +148,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckCorrect.json b/src/test/resources/checkers/emptyDemographicCheckCorrect.json index 676d9e06..34a612c0 100644 --- a/src/test/resources/checkers/emptyDemographicCheckCorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckCorrect.json @@ -462,7 +462,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json index f22ae1b2..9ac8d92f 100644 --- a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json @@ -458,7 +458,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyInclusionRules.json b/src/test/resources/checkers/emptyInclusionRules.json index d7def9e6..e0af1736 100644 --- a/src/test/resources/checkers/emptyInclusionRules.json +++ b/src/test/resources/checkers/emptyInclusionRules.json @@ -54,7 +54,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyPrimaryCriteriaList.json b/src/test/resources/checkers/emptyPrimaryCriteriaList.json index c4c7425d..acbd8ac3 100644 --- a/src/test/resources/checkers/emptyPrimaryCriteriaList.json +++ b/src/test/resources/checkers/emptyPrimaryCriteriaList.json @@ -39,7 +39,8 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckCorrect.json b/src/test/resources/checkers/eventsProgressionCheckCorrect.json index 8d8e1118..0cf117c6 100644 --- a/src/test/resources/checkers/eventsProgressionCheckCorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckCorrect.json @@ -82,7 +82,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json index 4ed760a3..5d927fb3 100644 --- a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json @@ -82,7 +82,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json index 121726e5..270f534d 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json @@ -478,7 +478,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json index 50a9a34f..afdc0ef0 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json @@ -268,7 +268,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/noExitCriteriaCheck.json b/src/test/resources/checkers/noExitCriteriaCheck.json index 1cc150d6..822578ab 100644 --- a/src/test/resources/checkers/noExitCriteriaCheck.json +++ b/src/test/resources/checkers/noExitCriteriaCheck.json @@ -77,7 +77,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json index 3b63bd3b..913dc418 100644 --- a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json +++ b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json @@ -77,7 +77,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json index 145d690a..79152e6b 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json @@ -1358,7 +1358,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json index 24864bf0..e1a3dd8b 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json @@ -701,7 +701,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/timePatternCheckCorrect.json b/src/test/resources/checkers/timePatternCheckCorrect.json index 75146327..5b80ee70 100644 --- a/src/test/resources/checkers/timePatternCheckCorrect.json +++ b/src/test/resources/checkers/timePatternCheckCorrect.json @@ -59,7 +59,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -80,7 +81,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -101,7 +103,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -123,7 +126,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/timePatternCheckIncorrect.json b/src/test/resources/checkers/timePatternCheckIncorrect.json index 964b92ad..1488cbe4 100644 --- a/src/test/resources/checkers/timePatternCheckIncorrect.json +++ b/src/test/resources/checkers/timePatternCheckIncorrect.json @@ -88,7 +88,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -109,7 +110,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -130,7 +132,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -152,7 +155,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -173,7 +177,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -194,7 +199,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -216,7 +222,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/unusedConceptSet.json b/src/test/resources/checkers/unusedConceptSet.json index 3cc898be..b37b5ee4 100644 --- a/src/test/resources/checkers/unusedConceptSet.json +++ b/src/test/resources/checkers/unusedConceptSet.json @@ -4433,7 +4433,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/unusedConceptSetCorrect.json b/src/test/resources/checkers/unusedConceptSetCorrect.json index 38143407..78dae501 100644 --- a/src/test/resources/checkers/unusedConceptSetCorrect.json +++ b/src/test/resources/checkers/unusedConceptSetCorrect.json @@ -4826,7 +4826,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json index 63fae6f7..35d19f07 100644 --- a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json +++ b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json @@ -287,11 +287,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -388,11 +390,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -508,11 +512,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -684,11 +690,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -795,11 +803,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1038,11 +1048,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1259,11 +1271,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1463,11 +1477,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1571,11 +1587,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1749,11 +1767,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1930,11 +1950,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2092,11 +2114,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2125,11 +2149,13 @@ }, "StartWindow": { "Start": { - "Days": 100, + "TimeUnitValue": 100, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 100, + "TimeUnitValue": 100, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2153,11 +2179,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2175,11 +2203,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2200,7 +2230,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "minute", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json index d7c071b6..bf25e20e 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json @@ -50,13 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 90 + "OffsetUnit": "day", + "OffsetUnitValue": 90 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": { "StartDate": "2000-04-01", diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json b/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json index 5561c5c2..c1f659b3 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-02-01", + "condition_start_datetime": "2000-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +29,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-07-01", + "condition_start_datetime": "2000-07-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +37,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json index b257b407..352fd00d 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json @@ -54,7 +54,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -78,7 +79,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json b/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json index 6487957d..d4ea63a1 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-07", + "condition_start_datetime": "2000-06-07 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-09-01", + "condition_start_datetime": "2000-09-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json index 6867ecd9..e2a4ee39 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json @@ -51,11 +51,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -73,11 +75,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -101,11 +105,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -123,11 +129,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -153,7 +161,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json index c119053c..2d3f8620 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json @@ -51,6 +51,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -58,6 +59,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -65,6 +67,7 @@ "person_id":3, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json index 481f28d1..b31aa4e6 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json @@ -52,14 +52,16 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -86,7 +88,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 541c70a4..8b983658 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -23,13 +23,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 0 + "OffsetUnit": "hour", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 30 + "EraPadUnit": "minute", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json index 915128f8..806f6510 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json @@ -14,7 +14,9 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-06-16", + "condition_start_datetime":"2009-06-16 00:00:00", "condition_end_date":"2009-06-19", + "condition_end_datetime":"2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -22,7 +24,9 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-07-01", + "condition_start_datetime":"2009-07-01 00:00:00", "condition_end_date":"2009-07-04", + "condition_end_datetime":"2009-07-04 00:00:00", "condition_type_concept_id":0 }, { @@ -30,7 +34,9 @@ "person_id":1, "condition_concept_id":3, "condition_start_date":"2009-07-27", + "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", + "condition_end_datetime":"2009-07-30 00:00:00", "condition_type_concept_id":0 }, { @@ -38,7 +44,9 @@ "person_id":1, "condition_concept_id":4, "condition_start_date":"2009-07-27", + "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", + "condition_end_datetime":"2009-07-30 00:00:00", "condition_type_concept_id":0 }, { @@ -46,7 +54,9 @@ "person_id":1, "condition_concept_id":5, "condition_start_date":"2009-07-27", + "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", + "condition_end_datetime":"2009-07-30 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json index f62ad136..4d640023 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json @@ -4,6 +4,18 @@ "cohort_definition_id":1, "subject_id":1, "cohort_start_date":"2009-06-16", + "cohort_end_date":"2009-06-19" + }, + { + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2009-07-01", + "cohort_end_date":"2009-07-04" + }, + { + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2009-07-27", "cohort_end_date":"2009-07-30" } ] diff --git a/src/test/resources/cohortgeneration/exits/censorEventExpression.json b/src/test/resources/cohortgeneration/exits/censorEventExpression.json index e2344be6..725f718c 100644 --- a/src/test/resources/cohortgeneration/exits/censorEventExpression.json +++ b/src/test/resources/cohortgeneration/exits/censorEventExpression.json @@ -50,7 +50,8 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 31 + "OffsetUnit": "day", + "OffsetUnitValue": 31 } }, "CensoringCriteria": [ @@ -60,7 +61,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json index 2d5f8d08..09efd2fe 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json @@ -57,7 +57,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index 5be887e2..b142ef89 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -57,7 +57,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json index 7c98a4a8..7faca602 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json @@ -50,13 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 99999 + "OffsetUnit": "day", + "OffsetUnitValue": 99999 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json index 1e446203..0f5ab2e2 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json @@ -50,13 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 31 + "OffsetUnit": "day", + "OffsetUnitValue": 31 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json index 89500baf..e66c806f 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json @@ -44,7 +44,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json index ae4d177b..70b64b4c 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json @@ -44,6 +44,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-07", + "condition_start_datetime": "2000-06-07 00:00:00", "condition_type_concept_id":0 }, { @@ -51,6 +52,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-09-01", + "condition_start_datetime": "2000-09-01 00:00:00", "condition_type_concept_id":0 }, { @@ -58,6 +60,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json index dc86948e..200fe127 100644 --- a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json +++ b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json @@ -86,7 +86,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -105,7 +106,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json index 7a2bf8d8..49a41753 100644 --- a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json +++ b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +29,7 @@ "person_id":1, "condition_concept_id":3, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +37,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/limits/limitExpression.json b/src/test/resources/cohortgeneration/limits/limitExpression.json index df0ce0ba..23ae40b8 100644 --- a/src/test/resources/cohortgeneration/limits/limitExpression.json +++ b/src/test/resources/cohortgeneration/limits/limitExpression.json @@ -54,7 +54,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -78,13 +79,15 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 1 + "OffsetUnit": "day", + "OffsetUnitValue": 1 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 60 + "EraPadUnit": "day", + "EraPadUnitValue": 60 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/limits/limitTest_PREP.json b/src/test/resources/cohortgeneration/limits/limitTest_PREP.json index af1b7504..cc846b9b 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_PREP.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-02-01", + "condition_start_datetime": "2000-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +30,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +38,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-07-01", + "condition_start_datetime": "2000-07-01 00:00:00", "condition_type_concept_id":0 }, { @@ -42,6 +46,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-08-01", + "condition_start_datetime": "2000-08-01 00:00:00", "condition_type_concept_id":0 }, { @@ -49,6 +54,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-11-01", + "condition_start_datetime": "2000-11-01 00:00:00", "condition_type_concept_id":0 }, { @@ -56,6 +62,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-12-01", + "condition_start_datetime": "2000-12-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json index ce44ba5f..eb27b963 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json @@ -1,40 +1,40 @@ { "limitTest.cohort": [ { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2000-02-01", - "cohort_end_date":"2000-02-02" + "cohort_definition_id": 1, + "subject_id": 1, + "cohort_start_date": "2000-02-01", + "cohort_end_date": "2000-02-02" }, { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2000-06-01", - "cohort_end_date":"2000-08-02" + "cohort_definition_id": 1, + "subject_id": 1, + "cohort_start_date": "2000-06-01", + "cohort_end_date": "2000-08-02" }, { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2000-11-01", - "cohort_end_date":"2000-12-02" + "cohort_definition_id": 1, + "subject_id": 1, + "cohort_start_date": "2000-11-01", + "cohort_end_date": "2000-12-02" }, { - "cohort_definition_id":2, - "subject_id":1, - "cohort_start_date":"2000-02-01", - "cohort_end_date":"2000-02-02" + "cohort_definition_id": 2, + "subject_id": 1, + "cohort_start_date": "2000-02-01", + "cohort_end_date": "2000-02-02" }, { - "cohort_definition_id":3, - "subject_id":1, - "cohort_start_date":"2000-02-01", - "cohort_end_date":"2000-02-02" + "cohort_definition_id": 3, + "subject_id": 1, + "cohort_start_date": "2000-02-01", + "cohort_end_date": "2000-02-02" }, { - "cohort_definition_id":5, - "subject_id":1, - "cohort_start_date":"2000-12-01", - "cohort_end_date":"2000-12-02" + "cohort_definition_id": 5, + "subject_id": 1, + "cohort_start_date": "2000-12-01", + "cohort_end_date": "2000-12-02" } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json index 0957bca8..99a748f6 100644 --- a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json +++ b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json @@ -71,7 +71,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json index 2e1ac335..0890f2ae 100644 --- a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json +++ b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-02-01", + "condition_start_datetime":"2000-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -22,6 +23,7 @@ "condition_concept_id":2, "condition_source_concept_id":3, "condition_start_date":"2000-07-01", + "condition_start_datetime":"2000-07-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/corelatedcriteria/distinctDefault_PREP.json b/src/test/resources/corelatedcriteria/distinctDefault_PREP.json index cb1ec56a..d86d6753 100644 --- a/src/test/resources/corelatedcriteria/distinctDefault_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctDefault_PREP.json @@ -1,90 +1,95 @@ { "cdm.person": [ { - "person_id":1, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 1, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 }, { - "person_id":2, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 2, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 } ], "cdm.condition_occurrence": [ { "condition_occurrence_id": 1, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2000-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 1 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 1 }, { "condition_occurrence_id": 2, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 3, - "person_id":1, - "condition_concept_id":3, - "condition_start_date":"2005-01-02", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 3, + "condition_start_date": "2005-01-02", + "condition_start_datetime": "2005-01-02 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 4, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 3 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 3 }, { "condition_occurrence_id": 5, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 4 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 4 } ], - "cdm.observation_period" : [ + "cdm.observation_period": [ { "observation_period_id": 1, - "person_id":1, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 1, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 }, { "observation_period_id": 2, - "person_id":2, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 2, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 } ], - "distinct_default.cohort" : [ + "distinct_default.cohort": [ { "cohort_definition_id": 1, - "subject_id":1, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 1, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" }, { "cohort_definition_id": 1, - "subject_id":2, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 2, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" } ] } \ No newline at end of file diff --git a/src/test/resources/corelatedcriteria/distinctStart_PREP.json b/src/test/resources/corelatedcriteria/distinctStart_PREP.json index 450e2cec..f87db1a2 100644 --- a/src/test/resources/corelatedcriteria/distinctStart_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctStart_PREP.json @@ -1,90 +1,95 @@ { "cdm.person": [ { - "person_id":1, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 1, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 }, { - "person_id":2, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 2, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 } ], "cdm.condition_occurrence": [ { "condition_occurrence_id": 1, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2000-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 1 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 1 }, { "condition_occurrence_id": 2, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 3, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2005-01-02", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2005-01-02", + "condition_start_datetime": "2005-01-02 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 4, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 3 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 3 }, { "condition_occurrence_id": 5, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 4 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 4 } ], - "cdm.observation_period" : [ + "cdm.observation_period": [ { "observation_period_id": 1, - "person_id":1, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 1, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 }, { "observation_period_id": 2, - "person_id":2, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 2, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 } ], - "distinct_start.cohort" : [ + "distinct_start.cohort": [ { "cohort_definition_id": 1, - "subject_id":1, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 1, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" }, { "cohort_definition_id": 1, - "subject_id":2, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 2, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" } ] } \ No newline at end of file diff --git a/src/test/resources/corelatedcriteria/distinctVisit_PREP.json b/src/test/resources/corelatedcriteria/distinctVisit_PREP.json index adbdb532..b6fb6186 100644 --- a/src/test/resources/corelatedcriteria/distinctVisit_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctVisit_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime":"2000-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 1 }, @@ -29,6 +30,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 2 }, @@ -37,6 +39,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-02", + "condition_start_datetime":"2005-01-02 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 2 }, @@ -45,6 +48,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 3 }, @@ -53,6 +57,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 4 } diff --git a/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json b/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json index ae85c790..8e8b9f2c 100644 --- a/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json +++ b/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime":"2000-01-01 01:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +29,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 01:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +37,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-02-01", + "condition_start_datetime":"2005-02-01 01:00:00", "condition_type_concept_id":0 }, { @@ -42,6 +45,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-03-01", + "condition_start_datetime":"2005-03-01 01:00:00", "condition_type_concept_id":0 }, { @@ -49,6 +53,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-04-01", + "condition_start_datetime":"2005-04-01 01:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json b/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json index a0f1dee9..63fe4bbc 100644 --- a/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json +++ b/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":1, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,7 +29,9 @@ "person_id":1, "condition_concept_id":1, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_end_date":"2000-07-01", + "condition_end_datetime": "2000-07-01 00:00:00", "condition_type_concept_id":0 }, { @@ -36,6 +39,7 @@ "person_id":1, "condition_concept_id":1, "condition_start_date":"2000-12-31", + "condition_start_datetime": "2000-12-31 00:00:00", "condition_type_concept_id":0 }, { @@ -43,6 +47,7 @@ "person_id":2, "condition_concept_id":1, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -50,6 +55,7 @@ "person_id":2, "condition_concept_id":1, "condition_start_date":"2000-12-31", + "condition_start_datetime": "2000-12-31 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/ddl/cdm_v5.0.sql b/src/test/resources/ddl/cdm_v5.0.sql index eb9ce9af..e4771e5f 100644 --- a/src/test/resources/ddl/cdm_v5.0.sql +++ b/src/test/resources/ddl/cdm_v5.0.sql @@ -119,8 +119,8 @@ CREATE TABLE @schemaName.attribute_definition ( ) ; -CREATE TABLE @schemaName.cdm_source - ( +CREATE TABLE @schemaName.cdm_source + ( cdm_source_name VARCHAR(255) NOT NULL, cdm_source_abbreviation VARCHAR(25) NULL, cdm_holder VARCHAR(255) NULL, @@ -131,46 +131,46 @@ CREATE TABLE @schemaName.cdm_source cdm_release_date DATE NULL, cdm_version VARCHAR(10) NULL, vocabulary_version VARCHAR(20) NULL - ) + ) ; -CREATE TABLE @schemaName.person +CREATE TABLE @schemaName.person ( - person_id INTEGER NOT NULL , - gender_concept_id INTEGER NOT NULL , - year_of_birth INTEGER NOT NULL , - month_of_birth INTEGER NULL, - day_of_birth INTEGER NULL, + person_id INTEGER NOT NULL , + gender_concept_id INTEGER NOT NULL , + year_of_birth INTEGER NOT NULL , + month_of_birth INTEGER NULL, + day_of_birth INTEGER NULL, time_of_birth VARCHAR(10) NULL, - race_concept_id INTEGER NOT NULL, - ethnicity_concept_id INTEGER NOT NULL, - location_id INTEGER NULL, - provider_id INTEGER NULL, - care_site_id INTEGER NULL, - person_source_value VARCHAR(50) NULL, + race_concept_id INTEGER NOT NULL, + ethnicity_concept_id INTEGER NOT NULL, + location_id INTEGER NULL, + provider_id INTEGER NULL, + care_site_id INTEGER NULL, + person_source_value VARCHAR(50) NULL, gender_source_value VARCHAR(50) NULL, - gender_source_concept_id INTEGER NULL, - race_source_value VARCHAR(50) NULL, - race_source_concept_id INTEGER NULL, + gender_source_concept_id INTEGER NULL, + race_source_value VARCHAR(50) NULL, + race_source_concept_id INTEGER NULL, ethnicity_source_value VARCHAR(50) NULL, ethnicity_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.observation_period - ( - observation_period_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - observation_period_start_date DATE NOT NULL , +CREATE TABLE @schemaName.observation_period + ( + observation_period_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + observation_period_start_date DATE NOT NULL , observation_period_end_date DATE NOT NULL , period_type_concept_id INTEGER NOT NULL - ) + ) ; CREATE TABLE @schemaName.specimen - ( + ( specimen_id INTEGER NOT NULL , person_id INTEGER NOT NULL , specimen_concept_id INTEGER NOT NULL , @@ -190,141 +190,142 @@ CREATE TABLE @schemaName.specimen ; -CREATE TABLE @schemaName.death - ( - person_id INTEGER NOT NULL , - death_date DATE NOT NULL , - death_type_concept_id INTEGER NOT NULL , - cause_concept_id INTEGER NULL , +CREATE TABLE @schemaName.death + ( + person_id INTEGER NOT NULL , + death_date DATE NOT NULL , + death_type_concept_id INTEGER NOT NULL , + cause_concept_id INTEGER NULL , cause_source_value VARCHAR(50) NULL, cause_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.visit_occurrence - ( - visit_occurrence_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - visit_concept_id INTEGER NOT NULL , - visit_start_date DATE NOT NULL , +CREATE TABLE @schemaName.visit_occurrence + ( + visit_occurrence_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + visit_concept_id INTEGER NOT NULL , + visit_start_date DATE NOT NULL , visit_start_time VARCHAR(10) NULL , visit_end_date DATE NOT NULL , - visit_end_time VARCHAR(10) NULL , + visit_end_time VARCHAR(10) NULL , visit_type_concept_id INTEGER NOT NULL , provider_id INTEGER NULL, - care_site_id INTEGER NULL, + care_site_id INTEGER NULL, visit_source_value VARCHAR(50) NULL, visit_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.procedure_occurrence - ( - procedure_occurrence_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - procedure_concept_id INTEGER NOT NULL , - procedure_date DATE NOT NULL , +CREATE TABLE @schemaName.procedure_occurrence + ( + procedure_occurrence_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + procedure_concept_id INTEGER NOT NULL , + procedure_date DATE NOT NULL , procedure_type_concept_id INTEGER NOT NULL , modifier_concept_id INTEGER NULL , - quantity INTEGER NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + quantity INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , procedure_source_value VARCHAR(50) NULL , procedure_source_concept_id INTEGER NULL , qualifier_source_value VARCHAR(50) NULL - ) -; - - -CREATE TABLE @schemaName.drug_exposure - ( - drug_exposure_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - drug_concept_id INTEGER NOT NULL , - drug_exposure_start_date DATE NOT NULL , - drug_exposure_end_date DATE NULL , - drug_type_concept_id INTEGER NOT NULL , - stop_reason VARCHAR(20) NULL , - refills INTEGER NULL , - quantity NUMERIC NULL , - days_supply INTEGER NULL , - sig TEXT NULL , + ) +; + + +CREATE TABLE @schemaName.drug_exposure + ( + drug_exposure_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + drug_concept_id INTEGER NOT NULL , + drug_exposure_start_date DATE NOT NULL , + drug_exposure_end_date DATE NULL , + drug_type_concept_id INTEGER NOT NULL , + stop_reason VARCHAR(20) NULL , + refills INTEGER NULL , + quantity NUMERIC NULL , + days_supply INTEGER NULL , + sig TEXT NULL , route_concept_id INTEGER NULL , effective_drug_dose NUMERIC NULL , dose_unit_concept_id INTEGER NULL , lot_number VARCHAR(50) NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , drug_source_value VARCHAR(50) NULL , drug_source_concept_id INTEGER NULL , route_source_value VARCHAR(50) NULL , dose_unit_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.device_exposure - ( - device_exposure_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - device_concept_id INTEGER NOT NULL , - device_exposure_start_date DATE NOT NULL , - device_exposure_end_date DATE NULL , - device_type_concept_id INTEGER NOT NULL , +CREATE TABLE @schemaName.device_exposure + ( + device_exposure_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + device_concept_id INTEGER NOT NULL , + device_exposure_start_date DATE NOT NULL , + device_exposure_end_date DATE NULL , + device_type_concept_id INTEGER NOT NULL , unique_device_id VARCHAR(50) NULL , quantity INTEGER NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , device_source_value VARCHAR(100) NULL , device_source_concept_id INTEGER NULL - ) -; - -CREATE TABLE @schemaName.condition_occurrence - ( - condition_occurrence_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - condition_concept_id INTEGER NOT NULL , - condition_start_date DATE NOT NULL , - condition_end_date DATE NULL , - condition_type_concept_id INTEGER NOT NULL , - stop_reason VARCHAR(20) NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + ) +; + +CREATE TABLE @schemaName.condition_occurrence + ( + condition_occurrence_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + condition_concept_id INTEGER NOT NULL , + condition_start_date DATE NOT NULL , + condition_start_datetime TIMESTAMP NOT NULL , + condition_end_date DATE NULL , + condition_end_datetime TIMESTAMP NULL , + condition_type_concept_id INTEGER NOT NULL , + stop_reason VARCHAR(20) NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , condition_source_value VARCHAR(50) NULL , condition_source_concept_id INTEGER NULL - ) -; + ); -CREATE TABLE @schemaName.measurement - ( - measurement_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - measurement_concept_id INTEGER NOT NULL , - measurement_date DATE NOT NULL , +CREATE TABLE @schemaName.measurement + ( + measurement_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + measurement_concept_id INTEGER NOT NULL , + measurement_date DATE NOT NULL , measurement_time VARCHAR(10) NULL , measurement_type_concept_id INTEGER NOT NULL , - operator_concept_id INTEGER NULL , - value_as_number NUMERIC NULL , - value_as_concept_id INTEGER NULL , - unit_concept_id INTEGER NULL , - range_low NUMERIC NULL , - range_high NUMERIC NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , - measurement_source_value VARCHAR(50) NULL , + operator_concept_id INTEGER NULL , + value_as_number NUMERIC NULL , + value_as_concept_id INTEGER NULL , + unit_concept_id INTEGER NULL , + range_low NUMERIC NULL , + range_high NUMERIC NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , + measurement_source_value VARCHAR(50) NULL , measurement_source_concept_id INTEGER NULL , unit_source_value VARCHAR(50) NULL , value_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.note - ( - note_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , +CREATE TABLE @schemaName.note + ( + note_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , note_date DATE NOT NULL , note_time VARCHAR(10) NULL , note_type_concept_id INTEGER NOT NULL , @@ -332,36 +333,36 @@ CREATE TABLE @schemaName.note provider_id INTEGER NULL , visit_occurrence_id INTEGER NULL , note_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.observation - ( - observation_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - observation_concept_id INTEGER NOT NULL , - observation_date DATE NOT NULL , - observation_time VARCHAR(10) NULL , - observation_type_concept_id INTEGER NOT NULL , - value_as_number NUMERIC NULL , - value_as_string VARCHAR(60) NULL , - value_as_concept_id INTEGER NULL , +CREATE TABLE @schemaName.observation + ( + observation_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + observation_concept_id INTEGER NOT NULL , + observation_date DATE NOT NULL , + observation_time VARCHAR(10) NULL , + observation_type_concept_id INTEGER NOT NULL , + value_as_number NUMERIC NULL , + value_as_string VARCHAR(60) NULL , + value_as_concept_id INTEGER NULL , qualifier_concept_id INTEGER NULL , - unit_concept_id INTEGER NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + unit_concept_id INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , observation_source_value VARCHAR(50) NULL , - observation_source_concept_id INTEGER NULL , + observation_source_concept_id INTEGER NULL , unit_source_value VARCHAR(50) NULL , qualifier_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.fact_relationship - ( - domain_concept_id_1 INTEGER NOT NULL , +CREATE TABLE @schemaName.fact_relationship + ( + domain_concept_id_1 INTEGER NOT NULL , fact_id_1 INTEGER NOT NULL , domain_concept_id_2 INTEGER NOT NULL , fact_id_2 INTEGER NOT NULL , @@ -369,187 +370,187 @@ CREATE TABLE @schemaName.fact_relationship ) ; -CREATE TABLE @schemaName.location - ( - location_id INTEGER NOT NULL , - address_1 VARCHAR(50) NULL , - address_2 VARCHAR(50) NULL , - city VARCHAR(50) NULL , - state VARCHAR(2) NULL , - zip VARCHAR(9) NULL , - county VARCHAR(20) NULL , +CREATE TABLE @schemaName.location + ( + location_id INTEGER NOT NULL , + address_1 VARCHAR(50) NULL , + address_2 VARCHAR(50) NULL , + city VARCHAR(50) NULL , + state VARCHAR(2) NULL , + zip VARCHAR(9) NULL , + county VARCHAR(20) NULL , location_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.care_site - ( - care_site_id INTEGER NOT NULL , +CREATE TABLE @schemaName.care_site + ( + care_site_id INTEGER NOT NULL , care_site_name VARCHAR(255) NULL , place_of_service_concept_id INTEGER NULL , - location_id INTEGER NULL , - care_site_source_value VARCHAR(50) NULL , + location_id INTEGER NULL , + care_site_source_value VARCHAR(50) NULL , place_of_service_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.provider - ( +CREATE TABLE @schemaName.provider + ( provider_id INTEGER NOT NULL , - provider_name VARCHAR(255) NULL , - NPI VARCHAR(20) NULL , - DEA VARCHAR(20) NULL , - specialty_concept_id INTEGER NULL , - care_site_id INTEGER NULL , + provider_name VARCHAR(255) NULL , + NPI VARCHAR(20) NULL , + DEA VARCHAR(20) NULL , + specialty_concept_id INTEGER NULL , + care_site_id INTEGER NULL , year_of_birth INTEGER NULL , gender_concept_id INTEGER NULL , - provider_source_value VARCHAR(50) NULL , + provider_source_value VARCHAR(50) NULL , specialty_source_value VARCHAR(50) NULL , - specialty_source_concept_id INTEGER NULL , + specialty_source_concept_id INTEGER NULL , gender_source_value VARCHAR(50) NULL , gender_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.payer_plan_period - ( - payer_plan_period_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - payer_plan_period_start_date DATE NOT NULL , - payer_plan_period_end_date DATE NOT NULL , - payer_source_value VARCHAR (50) NULL , - plan_source_value VARCHAR (50) NULL , - family_source_value VARCHAR (50) NULL - ) +CREATE TABLE @schemaName.payer_plan_period + ( + payer_plan_period_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + payer_plan_period_start_date DATE NOT NULL , + payer_plan_period_end_date DATE NOT NULL , + payer_source_value VARCHAR (50) NULL , + plan_source_value VARCHAR (50) NULL , + family_source_value VARCHAR (50) NULL + ) ; -CREATE TABLE @schemaName.visit_cost - ( - visit_cost_id INTEGER NOT NULL , - visit_occurrence_id INTEGER NOT NULL , +CREATE TABLE @schemaName.visit_cost + ( + visit_cost_id INTEGER NOT NULL , + visit_occurrence_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , - total_paid NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , + total_paid NUMERIC NULL , payer_plan_period_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.procedure_cost - ( - procedure_cost_id INTEGER NOT NULL , - procedure_occurrence_id INTEGER NOT NULL , +CREATE TABLE @schemaName.procedure_cost + ( + procedure_cost_id INTEGER NOT NULL , + procedure_occurrence_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , total_paid NUMERIC NULL , - revenue_code_concept_id INTEGER NULL , + revenue_code_concept_id INTEGER NULL , payer_plan_period_id INTEGER NULL , revenue_code_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.drug_cost +CREATE TABLE @schemaName.drug_cost ( - drug_cost_id INTEGER NOT NULL , - drug_exposure_id INTEGER NOT NULL , + drug_cost_id INTEGER NOT NULL , + drug_exposure_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , - total_paid NUMERIC NULL , - ingredient_cost NUMERIC NULL , - dispensing_fee NUMERIC NULL , - average_wholesale_price NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , + total_paid NUMERIC NULL , + ingredient_cost NUMERIC NULL , + dispensing_fee NUMERIC NULL , + average_wholesale_price NUMERIC NULL , payer_plan_period_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.device_cost +CREATE TABLE @schemaName.device_cost ( - device_cost_id INTEGER NOT NULL , - device_exposure_id INTEGER NOT NULL , + device_cost_id INTEGER NOT NULL , + device_exposure_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , - total_paid NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , + total_paid NUMERIC NULL , payer_plan_period_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.cohort - ( - cohort_definition_id INTEGER NOT NULL , +CREATE TABLE @schemaName.cohort + ( + cohort_definition_id INTEGER NOT NULL , subject_id INTEGER NOT NULL , - cohort_start_date DATE NOT NULL , + cohort_start_date DATE NOT NULL , cohort_end_date DATE NOT NULL - ) + ) ; -CREATE TABLE @schemaName.cohort_attribute - ( - cohort_definition_id INTEGER NOT NULL , - cohort_start_date DATE NOT NULL , - cohort_end_date DATE NOT NULL , - subject_id INTEGER NOT NULL , +CREATE TABLE @schemaName.cohort_attribute + ( + cohort_definition_id INTEGER NOT NULL , + cohort_start_date DATE NOT NULL , + cohort_end_date DATE NOT NULL , + subject_id INTEGER NOT NULL , attribute_definition_id INTEGER NOT NULL , value_as_number NUMERIC NULL , value_as_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.drug_era - ( - drug_era_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - drug_concept_id INTEGER NOT NULL , - drug_era_start_date DATE NOT NULL , - drug_era_end_date DATE NOT NULL , +CREATE TABLE @schemaName.drug_era + ( + drug_era_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + drug_concept_id INTEGER NOT NULL , + drug_era_start_date DATE NOT NULL , + drug_era_end_date DATE NOT NULL , drug_exposure_count INTEGER NULL , gap_days INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.dose_era +CREATE TABLE @schemaName.dose_era ( - dose_era_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - drug_concept_id INTEGER NOT NULL , + dose_era_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + drug_concept_id INTEGER NOT NULL , unit_concept_id INTEGER NOT NULL , dose_value NUMERIC NOT NULL , - dose_era_start_date DATE NOT NULL , - dose_era_end_date DATE NOT NULL - ) + dose_era_start_date DATE NOT NULL , + dose_era_end_date DATE NOT NULL + ) ; -CREATE TABLE @schemaName.condition_era - ( - condition_era_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - condition_concept_id INTEGER NOT NULL , - condition_era_start_date DATE NOT NULL , - condition_era_end_date DATE NOT NULL , +CREATE TABLE @schemaName.condition_era + ( + condition_era_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + condition_concept_id INTEGER NOT NULL , + condition_era_start_date DATE NOT NULL , + condition_era_end_date DATE NOT NULL , condition_occurrence_count INTEGER NULL - ) + ) ; diff --git a/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json b/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json index 287f1e2a..4c687fc7 100644 --- a/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json +++ b/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-07", + "condition_start_datetime": "2000-06-07 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-09-01", + "condition_start_datetime": "2000-09-01 00:00:00", "condition_type_concept_id":0 } ] diff --git a/src/test/resources/printfriendly/allAttributes.json b/src/test/resources/printfriendly/allAttributes.json index d52773ef..1c5573d4 100644 --- a/src/test/resources/printfriendly/allAttributes.json +++ b/src/test/resources/printfriendly/allAttributes.json @@ -70,18 +70,21 @@ }, "StartWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -141,7 +144,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -173,7 +177,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -318,7 +323,8 @@ "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -351,7 +357,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -373,7 +380,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -454,7 +462,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -543,7 +552,8 @@ "Coeff": -1 }, "End": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -565,7 +575,8 @@ "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -672,11 +683,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -711,7 +724,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -795,7 +809,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -816,7 +831,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/anyCondition.json b/src/test/resources/printfriendly/anyCondition.json index 9ce0fa02..73ef7919 100644 --- a/src/test/resources/printfriendly/anyCondition.json +++ b/src/test/resources/printfriendly/anyCondition.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/printfriendly/censorCriteria.json b/src/test/resources/printfriendly/censorCriteria.json index 5695f9a1..e2948c49 100644 --- a/src/test/resources/printfriendly/censorCriteria.json +++ b/src/test/resources/printfriendly/censorCriteria.json @@ -324,11 +324,13 @@ }, "StartWindow": { "Start": { - "Days": 365, + "TimeUnitValue": 365, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -356,7 +358,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/conceptSet_simple.json b/src/test/resources/printfriendly/conceptSet_simple.json index 4b4d6399..de95aee1 100644 --- a/src/test/resources/printfriendly/conceptSet_simple.json +++ b/src/test/resources/printfriendly/conceptSet_simple.json @@ -74,7 +74,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/conditionEra.json b/src/test/resources/printfriendly/conditionEra.json index f1742911..9c7865f5 100644 --- a/src/test/resources/printfriendly/conditionEra.json +++ b/src/test/resources/printfriendly/conditionEra.json @@ -37,22 +37,26 @@ }, "StartWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false }, "EndWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": 1 }, "End": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": true @@ -136,11 +140,13 @@ }, "StartWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -159,7 +165,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/conditionOccurrence.json b/src/test/resources/printfriendly/conditionOccurrence.json index ff1aea70..7cd95ca8 100644 --- a/src/test/resources/printfriendly/conditionOccurrence.json +++ b/src/test/resources/printfriendly/conditionOccurrence.json @@ -52,7 +52,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -204,7 +205,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": true @@ -242,7 +244,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -261,7 +264,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/continuousObservation_none.json b/src/test/resources/printfriendly/continuousObservation_none.json index 2cfada2c..9d63f1b4 100644 --- a/src/test/resources/printfriendly/continuousObservation_none.json +++ b/src/test/resources/printfriendly/continuousObservation_none.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/continuousObservation_post.json b/src/test/resources/printfriendly/continuousObservation_post.json index 5a2432f1..05538e32 100644 --- a/src/test/resources/printfriendly/continuousObservation_post.json +++ b/src/test/resources/printfriendly/continuousObservation_post.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/continuousObservation_prior.json b/src/test/resources/printfriendly/continuousObservation_prior.json index 7b578f9d..774f4a73 100644 --- a/src/test/resources/printfriendly/continuousObservation_prior.json +++ b/src/test/resources/printfriendly/continuousObservation_prior.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/continuousObservation_priorpost.json b/src/test/resources/printfriendly/continuousObservation_priorpost.json index 5c37ec76..56abf230 100644 --- a/src/test/resources/printfriendly/continuousObservation_priorpost.json +++ b/src/test/resources/printfriendly/continuousObservation_priorpost.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/countCriteria.json b/src/test/resources/printfriendly/countCriteria.json index dc46760f..d5561bd3 100644 --- a/src/test/resources/printfriendly/countCriteria.json +++ b/src/test/resources/printfriendly/countCriteria.json @@ -30,11 +30,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -76,7 +78,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -97,7 +100,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -236,7 +240,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -267,7 +272,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -297,7 +303,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -330,7 +337,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -348,7 +356,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -375,11 +384,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -400,7 +411,8 @@ "Coeff": -1 }, "End": { - "Days": 31, + "TimeUnitValue": 31, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -421,7 +433,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/printfriendly/countDistinctCriteria.json b/src/test/resources/printfriendly/countDistinctCriteria.json index 69a6d669..45bed9c7 100644 --- a/src/test/resources/printfriendly/countDistinctCriteria.json +++ b/src/test/resources/printfriendly/countDistinctCriteria.json @@ -30,11 +30,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -77,7 +79,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -104,7 +107,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -127,7 +131,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -168,7 +173,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/customEraExit.json b/src/test/resources/printfriendly/customEraExit.json index 06015aa7..e74f2963 100644 --- a/src/test/resources/printfriendly/customEraExit.json +++ b/src/test/resources/printfriendly/customEraExit.json @@ -50,7 +50,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/dateOffset.json b/src/test/resources/printfriendly/dateOffset.json index 0ac9dd93..f52faaeb 100644 --- a/src/test/resources/printfriendly/dateOffset.json +++ b/src/test/resources/printfriendly/dateOffset.json @@ -42,13 +42,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 7 + "OffsetUnit": "day", + "OffsetUnitValue": 7 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/death.json b/src/test/resources/printfriendly/death.json index a1927e16..4c232614 100644 --- a/src/test/resources/printfriendly/death.json +++ b/src/test/resources/printfriendly/death.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -173,7 +174,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/deviceExposure.json b/src/test/resources/printfriendly/deviceExposure.json index 0bb9432b..3f6a4e10 100644 --- a/src/test/resources/printfriendly/deviceExposure.json +++ b/src/test/resources/printfriendly/deviceExposure.json @@ -66,7 +66,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -180,7 +181,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -215,11 +217,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -238,7 +242,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/doseEra.json b/src/test/resources/printfriendly/doseEra.json index ccbc833e..1c1493d4 100644 --- a/src/test/resources/printfriendly/doseEra.json +++ b/src/test/resources/printfriendly/doseEra.json @@ -38,11 +38,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -60,11 +62,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -168,11 +172,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -190,11 +196,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -232,7 +240,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -257,7 +266,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -276,7 +286,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/drugEra.json b/src/test/resources/printfriendly/drugEra.json index fc27a96f..6f8247bc 100644 --- a/src/test/resources/printfriendly/drugEra.json +++ b/src/test/resources/printfriendly/drugEra.json @@ -41,7 +41,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -158,7 +159,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -180,7 +182,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/drugExposure.json b/src/test/resources/printfriendly/drugExposure.json index fb50b1e2..b2ad53e5 100644 --- a/src/test/resources/printfriendly/drugExposure.json +++ b/src/test/resources/printfriendly/drugExposure.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -58,11 +59,13 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -247,7 +250,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/measurement.json b/src/test/resources/printfriendly/measurement.json index 505a8988..0f5f0bc7 100644 --- a/src/test/resources/printfriendly/measurement.json +++ b/src/test/resources/printfriendly/measurement.json @@ -41,7 +41,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -59,7 +60,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -261,7 +263,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/noCensorCriteria.json b/src/test/resources/printfriendly/noCensorCriteria.json index 9c224c31..dd045db5 100644 --- a/src/test/resources/printfriendly/noCensorCriteria.json +++ b/src/test/resources/printfriendly/noCensorCriteria.json @@ -37,8 +37,8 @@ } ], "ObservationWindow":{ - "PriorDays":0, - "PostDays":0 + "PriorDays": 0, + "PostDays": 0 }, "PrimaryCriteriaLimit":{ "Type":"First" @@ -398,6 +398,7 @@ ], "CollapseSettings":{ "CollapseType":"ERA", - "EraPad":0 + "EraPadUnit": "days", + "EraPadUnitValue": 0 } } \ No newline at end of file diff --git a/src/test/resources/printfriendly/nullCodesetId.json b/src/test/resources/printfriendly/nullCodesetId.json index b2e94068..03ec724a 100644 --- a/src/test/resources/printfriendly/nullCodesetId.json +++ b/src/test/resources/printfriendly/nullCodesetId.json @@ -376,7 +376,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/observation.json b/src/test/resources/printfriendly/observation.json index b72d3886..561af4c1 100644 --- a/src/test/resources/printfriendly/observation.json +++ b/src/test/resources/printfriendly/observation.json @@ -41,7 +41,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -216,7 +217,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/observationPeriod_1.json b/src/test/resources/printfriendly/observationPeriod_1.json index fb96e549..a55605eb 100644 --- a/src/test/resources/printfriendly/observationPeriod_1.json +++ b/src/test/resources/printfriendly/observationPeriod_1.json @@ -35,7 +35,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -119,7 +120,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/procedureOccurrence.json b/src/test/resources/printfriendly/procedureOccurrence.json index d29c1e89..5118c603 100644 --- a/src/test/resources/printfriendly/procedureOccurrence.json +++ b/src/test/resources/printfriendly/procedureOccurrence.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -190,7 +191,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/specimen.json b/src/test/resources/printfriendly/specimen.json index 6726d52d..f577d458 100644 --- a/src/test/resources/printfriendly/specimen.json +++ b/src/test/resources/printfriendly/specimen.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -175,7 +176,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/visit.json b/src/test/resources/printfriendly/visit.json index c1486b3e..a6296603 100644 --- a/src/test/resources/printfriendly/visit.json +++ b/src/test/resources/printfriendly/visit.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -176,7 +177,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json index a3dd827b..211b685c 100644 --- a/src/test/resources/printfriendly/visitDetail.json +++ b/src/test/resources/printfriendly/visitDetail.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -108,7 +109,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json b/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json index 9878b393..2b315b06 100644 --- a/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json +++ b/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime":"2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +30,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-02-01", + "condition_start_datetime":"2005-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +38,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-03-01", + "condition_start_datetime":"2005-03-01 00:00:00", "condition_type_concept_id":0 }, { @@ -42,6 +46,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-04-01", + "condition_start_datetime":"2005-04-01 00:00:00", "condition_type_concept_id":0 } ], From b9b58204e925a845f79efe458e15df896b35cd01 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Fri, 29 Dec 2023 20:18:16 +0700 Subject: [PATCH 02/25] update --- .../circe/check/checkers/Comparisons.java | 45 +++++++------- .../checkers/ExitCriteriaDaysOffsetCheck.java | 25 +++++--- .../checkers/FirstTimeInHistoryCheck.java | 7 ++- .../circe/check/checkers/RangeCheck.java | 26 ++++++--- .../check/checkers/TimePatternCheck.java | 25 +++++--- .../CohortExpressionQueryBuilder.java | 58 ++++++++++++------- .../cohortdefinition/CollapseSettings.java | 4 +- .../cohortdefinition/DateOffsetStrategy.java | 5 +- .../circe/cohortdefinition/IntervalUnit.java | 1 - .../ConditionOccurrenceSqlBuilder.java | 10 +--- .../printfriendly/cohortExpression.ftl | 4 ++ .../printfriendly/criteriaTypes.ftl | 49 +++++++++++++--- .../printfriendly/endStrategyTypes.ftl | 7 ++- .../printfriendly/inputTypes.ftl | 36 +++++++++--- .../sql/dateOffsetStrategy.sql | 23 ++++++-- .../cohortdefinition/sql/generateCohort.sql | 10 +++- 16 files changed, 228 insertions(+), 107 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 82ffe189..697b746e 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -20,9 +20,7 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -31,6 +29,12 @@ public class Comparisons { + private static final Map TIME_UNIT_CONVERSION = new HashMap<>(); + static { + TIME_UNIT_CONVERSION.put(IntervalUnit.HOUR.getName(), 60 * 60); + TIME_UNIT_CONVERSION.put(IntervalUnit.MINUTE.getName(), 60); + } + public static Boolean startIsGreaterThanEnd(NumericRange r) { return Objects.nonNull(r.value) && Objects.nonNull(r.extent) && r.value.intValue() > r.extent.intValue(); @@ -101,31 +105,26 @@ public static Predicate compare(Concept source) { .append(concept.vocabularyId, source.vocabularyId) .build(); } - public static int compareTo(ObservationFilter filter, Window window) { - int range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; - int range2Start = getTimeInSeconds(window.start); - int range2End = getTimeInSeconds(window.end); + int range1 , range2Start , range2End; + if(Objects.nonNull(window.start) && Objects.nonNull(window.start.days)){ + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + }else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); + } return range1 - (range2End - range2Start); } - private static int getTimeInSeconds(Window.Endpoint endpoint) { - if (Objects.isNull(endpoint)) { - return 0; + return Optional.ofNullable(endpoint) + .map(ep -> { + int convertRate = TIME_UNIT_CONVERSION.getOrDefault(ep.timeUnit, 1); + return Objects.nonNull(ep.timeUnitValue) ? ep.coeff * ep.timeUnitValue * convertRate : 0; + }).orElse(0); } - int convertRate; - if (endpoint.timeUnit.equals(IntervalUnit.DAY.getName())) { - convertRate = 24 * 60 * 60; - } else if (endpoint.timeUnit.equals(IntervalUnit.HOUR.getName())) { - convertRate = 60 * 60; - } else if (endpoint.timeUnit.equals(IntervalUnit.MINUTE.getName())) { - convertRate = 60; - } else convertRate = 1; - return Objects.nonNull(endpoint.timeUnitValue) - ? endpoint.coeff * endpoint.timeUnitValue * convertRate - : 0; - } - public static boolean compare(Criteria c1, Criteria c2) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index 1264be55..3d814885 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -18,14 +18,17 @@ package org.ohdsi.circe.check.checkers; -import static org.ohdsi.circe.check.operations.Operations.match; -import static org.ohdsi.circe.cohortdefinition.DateOffsetStrategy.DateField.StartDate; - -import java.util.Objects; import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.cohortdefinition.CohortExpression; import org.ohdsi.circe.cohortdefinition.DateOffsetStrategy; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static org.ohdsi.circe.check.operations.Operations.match; +import static org.ohdsi.circe.cohortdefinition.DateOffsetStrategy.DateField.StartDate; + public class ExitCriteriaDaysOffsetCheck extends BaseCheck { private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: %ss offset from start date should be greater than 0"; @@ -40,9 +43,15 @@ protected WarningSeverity defineSeverity() { protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) - .isA(DateOffsetStrategy.class) - .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) - .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); + .isA(DateOffsetStrategy.class) + .then(s -> match((DateOffsetStrategy)s) + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offset || 0 == dateOffsetStrategy.offsetUnitValue)) + .then(dateOffsetStrategy -> { + if(0 == dateOffsetStrategy.offset){ + reporter.add(String.format(DAYS_OFFSET_WARNING), "Days"); + }else { + reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)); + } + })); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 95f2109f..3c6db7c9 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -43,8 +43,11 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.timeUnitValue != null) || (c.startWindow.end != null - && c.startWindow.end.timeUnitValue != null))) + && c.startWindow.start.days != null) || (c.startWindow.end != null + && c.startWindow.end.days != null)) + || ((c.startWindow.start.timeUnitValue != null) + && (c.startWindow.end != null) + && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index 6b927308..01d4cdbd 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -25,6 +25,7 @@ import org.ohdsi.circe.cohortdefinition.Window; import java.util.Objects; +import java.util.Optional; public class RangeCheck extends BaseValueCheck { private static final String NEGATIVE_VALUE_ERROR = "Time window in criteria \"%s\" has negative value %d at %s"; @@ -52,15 +53,24 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep } private void checkWindow(Window window, WarningReporter reporter, String name) { + Optional.ofNullable(window) + .map(w -> checkEndpoint(w, name, "start")) + .ifPresent(reporter::add); - if (Objects.nonNull(window)) { - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.timeUnitValue) && window.start.timeUnitValue < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.timeUnitValue, "start"); - } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.timeUnitValue) && window.end.timeUnitValue < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.timeUnitValue, "end"); - } - } + Optional.ofNullable(window) + .map(w -> checkEndpoint(w, name, "end")) + .ifPresent(reporter::add); + } + + private String checkEndpoint(Window window, String name, String endpointType) { + boolean hasValid = Objects.nonNull(window.start) && Objects.nonNull(window.start.days < 0 ? window.start.days : window.start.timeUnitValue) && window.start.days < 0 ? window.start.days < 0 : window.start.timeUnitValue < 0; + return Optional.of(window) + .filter(w -> hasValid) + .map(w -> String.format(NEGATIVE_VALUE_ERROR, name, getEndpointValue(w.start), endpointType)) + .orElse(null); + } + private Object getEndpointValue(Window.Endpoint endpoint) { + return Objects.nonNull(endpoint.days) ? endpoint.days : endpoint.timeUnitValue; } private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java index 2a3c5407..561bf9b7 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java @@ -18,10 +18,7 @@ package org.ohdsi.circe.check.checkers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import org.ohdsi.circe.check.WarningSeverity; @@ -83,17 +80,29 @@ private String formatTimeWindow(TimeWindowInfo ti) { } private String formatDays(Window.Endpoint endpoint) { - return Objects.nonNull(endpoint.timeUnitValue) ? String.valueOf(endpoint.timeUnitValue) : "all"; + return Objects.nonNull(endpoint.days) ? String.valueOf(endpoint.days) : + Objects.nonNull(endpoint.timeUnitValue) ? String.valueOf(endpoint.timeUnitValue) : "all"; + } private String formatCoeff(Window.Endpoint endpoint) { return endpoint.coeff < 0 ? "before " : "after "; } + private Integer startDays(Window window) { - return Objects.nonNull(window) && Objects.nonNull(window.start) ? - (Objects.nonNull(window.start.timeUnitValue) ? window.start.timeUnitValue : 0) * window.start.coeff : 0; - } + return Optional.ofNullable(window) + .map(w -> w.start) + .map(start -> { + int coefficient = Optional.ofNullable(start.coeff).orElse(0); + return Optional.ofNullable(start.days) + .map(days -> days * coefficient) + .orElseGet(() -> Optional.ofNullable(start.timeUnitValue) + .map(timeUnitValue -> timeUnitValue * coefficient) + .orElse(0)); + }) + .orElse(0); +} class TimeWindowInfo { private String name; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 50071030..690d3675 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -20,9 +20,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; + +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang3.StringUtils; @@ -351,10 +350,12 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); -// resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); - resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); - resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); - + if(!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && expression.collapseSettings.eraPad != 0){ + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + }else{ + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); + } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByPersonQuery", getInclusionAnalysisQuery("#best_events", 1)); @@ -548,20 +549,25 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); + if (startWindow.start.days != null) { + startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); + } else if (startWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); } else { - startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (startExpression != null) { clauses.add(String.format("%s >= %s", startEventDateExpression, startExpression)); } - if (startWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); + if (startWindow.end.days != null) { + endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); + } + else if (startWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); } else { - endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (endExpression != null) { @@ -575,20 +581,24 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit String endIndexDateExpression = (endWindow.useIndexEnd != null && endWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; // for backwards compatability, having a null endWindow.useIndexEnd means they SHOULD use the index end date. String endEventDateExpression = (endWindow.useEventEnd == null || endWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (endWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); + if (endWindow.start.days != null) { + startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); + } else if (endWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); } else { - startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (startExpression != null) { clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); } - if (endWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); + if (endWindow.end.days != null) { + endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); + } else if (endWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); } else { - endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (endExpression != null) { @@ -765,10 +775,13 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); - strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + if(strat.offset != 0){ + strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + }else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); - return strategySql; } @@ -793,4 +806,5 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { } // + } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 0850fb71..26170865 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -24,8 +24,10 @@ public class CollapseSettings { @JsonProperty("CollapseType") public CollapseType collapseType = CollapseType.ERA; + @JsonProperty("EraPad") + public int eraPad = 0; @JsonProperty("EraPadUnit") - public String eraPadUnit = IntervalUnit.DAY.getName(); + public String eraPadUnit; @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 8f610267..f2d0ed6b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -30,14 +30,15 @@ public enum DateField { StartDate, EndDate } - + @JsonProperty("Offset") + public int offset = 0; @JsonProperty("DateField") public DateField dateField = DateField.StartDate; @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; @JsonProperty("OffsetUnit") - public String offsetUnit = IntervalUnit.DAY.getName(); + public String offsetUnit; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java index e55b58e7..2a40b9cb 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -3,7 +3,6 @@ import jdk.nashorn.internal.objects.annotations.Getter; public enum IntervalUnit { - DAY("day"), HOUR("hour"), MINUTE("minute"), SECOND("second"); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 35defc8a..8ca6c2a5 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -4,12 +4,9 @@ import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; import org.ohdsi.circe.helper.ResourceHelper; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.text.SimpleDateFormat; +import java.util.*; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -65,7 +62,6 @@ protected String embedCodesetClause(String query, T criteria) { @Override protected String embedOrdinalExpression(String query, T criteria, List whereClauses) { - // first if (criteria.first != null && criteria.first == true) { whereClauses.add("C.ordinal = 1"); diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl index c79088f3..5a2f6e12 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl @@ -64,7 +64,11 @@ ${cc?counter}. <@ct.Criteria c=cc/> ### Cohort Eras +<#if collapseSettings.eraPad?has_content && collapseSettings.eraPad != 0> +Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPad} days of each other. +<#else > Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPadUnitValue} ${collapseSettings.eraPadUnit}s of each other. + <#-- main template: end --> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index 3dc2ee4a..29d2bf84 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -279,15 +279,46 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi <#macro CountCriteria countCriteria level=0 indexLabel="cohort entry">having <#if countCriteria.occurrence.type == 0 && countCriteria.occurrence.count == 0>no<#else>${inputTypes.getCountType(countCriteria)} ${countCriteria.occurrence.count}<#-- --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> -<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> -<#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> -<#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> -<#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if -windowParts?size gt 0>${windowParts?join(" and ")}<#if restrictParts?size gt 0>; <#if -restrictParts?size gt 0>${restrictParts?join(" and ")} - +<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]> + <#if countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??> + <#local temp> + <@inputTypes.Window countCriteria.startWindow indexLabel /> + + <#local windowParts+=[temp]> + + <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> +<#local temp> + <@inputTypes.Window countCriteria.endWindow indexLabel /> + + <#local windowParts+=[temp]> + + <#if countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??> + <#local temp> + <@inputTypes.Window countCriteria.startWindow indexLabel /> + + <#local windowParts+=[temp]> + + <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> + <#local temp> + <@inputTypes.Window countCriteria.endWindow indexLabel /> + + <#local windowParts+=[temp]> + +<#if countCriteria.restrictVisit!false> + <#local temp>at same visit as ${indexLabel} + + <#local restrictParts+=[temp]> + +<#if countCriteria.ignoreObservationPeriod!false> + <#local temp>allow events outside observation period + <#local restrictParts+=[temp]> + + <#if windowParts?size gt 0>${windowParts?join(" and ")} + <#if restrictParts?size gt 0>; + + <#if restrictParts?size gt 0>${restrictParts?join(" and ")} + + <#-- Demographic Criteria --> <#macro DemographicCriteria c level=0 indexLabel = "cohort entry"><#local attrs = []><#local temp><@AgeGenderCriteria ageAtStart=c.age!{} gender=c.gender!{} /><#if temp?has_content><#local attrs+=[temp]><#local diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index a71c1bf4..15e16a34 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -24,7 +24,12 @@ The person exits the cohort at the end of continuous observation. <#assign dateOffsetFieldOptions = [{"id": "StartDate", "name": "start date"}, {"id": "EndDate", "name": "end date"}]> <#macro DateOffsetStrategy s> -The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. + <#if s.offset?? && s.offset == "day" > + The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offset "day"/>. + <#else > + The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. + + <#macro CustomEraStrategy s> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index d26b4cf1..3ab55c30 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -89,15 +89,37 @@ END Note!!!! <#function whichIndexPart useEnd><#if useEnd><#return "end date"><#else><#return "start date"> <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> -<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- ---><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} +<#----> +<#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 > + anytime on or before ${indexLabel} + ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1> +<#if w.start.days??>in the ${w.start.days} days +<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} + <#-- User Defined Period --> <#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if -p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} +p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} + + diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index b79289dd..cac7c35f 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,9 +1,20 @@ -- date offset strategy -select event_id, - person_id, - case - when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date - else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date +SELECT + event_id, + person_id, + CASE + WHEN @offset IS NOT NULL + THEN + CASE + WHEN DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date THEN op_end_date + ELSE DATEADD(@offsetUnit, @offsetUnitValue, @dateField) + END + ELSE + CASE + WHEN DATEADD(day, @offset, @dateField) > op_end_date THEN op_end_date + ELSE DATEADD(day, @offset, @dateField) + END + END AS end_date INTO #strategy_ends -from @eventTable; +FROM @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 0a873c67..8fad4578 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -62,8 +62,14 @@ from ( --cteEnds FROM #cohort_rows c JOIN ( -- cteEndDates SELECT - person_id - , DATEADD(@era_pad_unit, -1 * @era_pad, event_date) as end_date + person_id, + CASE + WHEN + @eraconstructorpad IS NOT NULL + THEN + DATEADD(day, -1 * @eraconstructorpad, event_date) + ELSE + DATEADD(@era_pad_unit, -1 * @era_pad, event_date) END AS end_date FROM ( SELECT From 6fd8ecd5b6a3791cf65ed9db8e973a6237e2cda0 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Thu, 4 Jan 2024 10:03:27 +0700 Subject: [PATCH 03/25] update/412023 --- .editorconfig | 2 +- .../circe/check/checkers/Comparisons.java | 20 ++-- .../checkers/ExitCriteriaDaysOffsetCheck.java | 10 +- .../checkers/FirstTimeInHistoryCheck.java | 10 +- .../circe/check/checkers/RangeCheck.java | 27 ++--- .../check/checkers/TimePatternCheck.java | 2 +- .../CohortExpressionQueryBuilder.java | 55 +++++---- .../cohortdefinition/CollapseSettings.java | 6 +- .../cohortdefinition/CustomEraStrategy.java | 8 ++ .../cohortdefinition/DateOffsetStrategy.java | 4 +- .../circe/cohortdefinition/IntervalUnit.java | 1 + .../ohdsi/circe/cohortdefinition/Window.java | 8 +- .../ConditionOccurrenceSqlBuilder.java | 3 +- .../printfriendly/criteriaTypes.ftl | 48 ++------ .../printfriendly/endStrategyTypes.ftl | 7 +- .../printfriendly/inputTypes.ftl | 104 +++++++----------- .../sql/customEraStrategy.sql | 6 +- .../sql/dateOffsetStrategy.sql | 23 +--- .../cohortdefinition/sql/generateCohort.sql | 11 +- .../circe/check/checkers/ComparisonsTest.java | 95 ++++++++++++++-- .../builders/CriteriaUtils.java | 3 +- .../printfriendly/PrintFriendlyTest.java | 6 +- .../additionalCriteriaCheckValueCorrect.json | 3 +- ...additionalCriteriaCheckValueIncorrect.json | 5 +- .../censoringEventCheckValueCorrect.json | 3 +- .../censoringEventCheckValueIncorrect.json | 3 +- .../conceptSetCriteriaCheckCorrect.json | 3 +- .../conceptSetCriteriaCheckIncorrect.json | 3 +- .../contradictionsCriteriaCheckCorrect.json | 3 +- .../contradictionsCriteriaCheckIncorrect.json | 3 +- .../checkers/deathTimeWindowCheckCorrect.json | 12 +- .../deathTimeWindowCheckIncorrect.json | 12 +- .../checkers/domainTypeCheckCorrect.json | 3 +- .../checkers/domainTypeCheckIncorrect.json | 3 +- .../checkers/drugDomainCheckCorrect.json | 9 +- .../checkers/drugDomainCheckIncorrect.json | 9 +- .../checkers/drugEraCheckCorrect.json | 9 +- .../duplicatesConceptSetCheckCorrect.json | 3 +- .../duplicatesConceptSetCheckIncorrect.json | 3 +- .../duplicatesCriteriaCheckCorrect.json | 3 +- .../duplicatesCriteriaCheckIncorrect.json | 3 +- .../checkers/emptyCensoringCriteriaList.json | 3 +- .../checkers/emptyCorrelatedCriteria.json | 3 +- .../emptyDemographicCheckCorrect.json | 9 +- .../emptyDemographicCheckIncorrect.json | 9 +- .../checkers/emptyInclusionRules.json | 3 +- .../checkers/emptyPrimaryCriteriaList.json | 3 +- .../eventsProgressionCheckCorrect.json | 9 +- .../eventsProgressionCheckIncorrect.json | 9 +- .../inclusionRulesCheckValueCorrect.json | 3 +- .../inclusionRulesCheckValueIncorrect.json | 3 +- .../checkers/noExitCriteriaCheck.json | 5 +- .../noExitCriteriaCheckEarliestEvent.json | 3 +- .../primaryCriteriaCheckValueCorrect.json | 3 +- .../primaryCriteriaCheckValueIncorrect.json | 3 +- .../checkers/timePatternCheckCorrect.json | 12 +- .../checkers/timePatternCheckIncorrect.json | 21 ++-- .../resources/checkers/unusedConceptSet.json | 3 +- .../checkers/unusedConceptSetCorrect.json | 3 +- .../allCriteria/allCriteriaExpression.json | 95 ++++++---------- .../correlatedCriteria/countsExpression.json | 6 +- .../correlatedCriteria/groupExpression.json | 17 ++- .../correlatedCriteria/visitExpression.json | 7 +- .../eraDupes/eraDupesExpression.json | 2 + .../eraDupes/eraDupes_VERIFY.json | 14 +-- .../exits/censorEventExpression.json | 2 + .../continuousExposureCensorExpression.json | 7 +- .../exits/continuousExposureExpression.json | 7 +- .../exits/fixedOffsetCensorExpression.json | 2 + .../exits/fixedOffsetExpression.json | 2 + .../firstOccurrenceTestExpression.json | 1 + .../inclusionRules/simpleInclusionRule.json | 2 + .../limits/limitExpression.json | 5 +- .../limits/limitTest_VERIFY.json | 30 +---- .../printfriendly/allAttributes.json | 39 +++++-- .../resources/printfriendly/anyCondition.json | 1 + .../printfriendly/censorCriteria.json | 5 +- .../printfriendly/conceptSet_simple.json | 1 + .../resources/printfriendly/conditionEra.json | 15 ++- .../printfriendly/conditionOccurrence.json | 7 +- .../continuousObservation_none.json | 1 + .../continuousObservation_post.json | 1 + .../continuousObservation_prior.json | 1 + .../continuousObservation_priorpost.json | 1 + .../printfriendly/countCriteria.json | 24 +++- .../printfriendly/countDistinctCriteria.json | 16 ++- .../printfriendly/customEraExit.json | 5 + .../resources/printfriendly/dateOffset.json | 2 + src/test/resources/printfriendly/death.json | 3 +- .../printfriendly/deviceExposure.json | 8 +- src/test/resources/printfriendly/doseEra.json | 26 +++-- src/test/resources/printfriendly/drugEra.json | 5 +- .../resources/printfriendly/drugExposure.json | 1 + .../resources/printfriendly/measurement.json | 9 +- .../printfriendly/noCensorCriteria.json | 1 + .../printfriendly/nullCodesetId.json | 1 + .../resources/printfriendly/observation.json | 2 + .../printfriendly/observationPeriod_1.json | 2 + .../printfriendly/procedureOccurrence.json | 4 +- .../resources/printfriendly/specimen.json | 4 +- src/test/resources/printfriendly/visit.json | 4 +- .../resources/printfriendly/visitDetail.json | 2 + 102 files changed, 555 insertions(+), 496 deletions(-) diff --git a/.editorconfig b/.editorconfig index 11a58414..d6d2a1b6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,4 @@ root = true [*.{java,sql}] charset = utf-8 indent_style = space -indent_size = 2 +indent_size = 4 diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 697b746e..8bffe26e 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -23,6 +23,7 @@ import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; + import org.apache.commons.lang3.builder.EqualsBuilder; import org.ohdsi.circe.cohortdefinition.*; import org.ohdsi.circe.vocabulary.Concept; @@ -105,16 +106,17 @@ public static Predicate compare(Concept source) { .append(concept.vocabularyId, source.vocabularyId) .build(); } + public static int compareTo(ObservationFilter filter, Window window) { - int range1 , range2Start , range2End; - if(Objects.nonNull(window.start) && Objects.nonNull(window.start.days)){ - range1 = filter.postDays + filter.priorDays; - range2Start = window.start.coeff * window.start.days; - range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; - }else { - range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; - range2Start = getTimeInSeconds(window.start); - range2End = getTimeInSeconds(window.end); + int range1, range2Start, range2End; + if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) && window.start.days != 0)) { + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + } else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); } return range1 - (range2End - range2Start); } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index 3d814885..e4294203 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -45,13 +45,7 @@ protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) .isA(DateOffsetStrategy.class) .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offset || 0 == dateOffsetStrategy.offsetUnitValue)) - .then(dateOffsetStrategy -> { - if(0 == dateOffsetStrategy.offset){ - reporter.add(String.format(DAYS_OFFSET_WARNING), "Days"); - }else { - reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)); - } - })); + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 3c6db7c9..89e437eb 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -42,12 +42,10 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni String name = CriteriaNameHelper.getCriteriaName(criteria.criteria) + " at " + groupName; Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) - .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.days != null) || (c.startWindow.end != null - && c.startWindow.end.days != null)) - || ((c.startWindow.start.timeUnitValue != null) - && (c.startWindow.end != null) - && c.startWindow.end.timeUnitValue != null)) + .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) + || (c.startWindow.end != null && c.startWindow.end.days != null)) + || c.startWindow != null && (( c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) + || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index 01d4cdbd..88606c5f 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -53,26 +53,23 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep } private void checkWindow(Window window, WarningReporter reporter, String name) { - Optional.ofNullable(window) - .map(w -> checkEndpoint(w, name, "start")) - .ifPresent(reporter::add); + if (Objects.isNull(window)) { + return; + } + checkAndReportIfNegative(window.start, reporter, name, "start"); + checkAndReportIfNegative(window.end, reporter, name, "end"); - Optional.ofNullable(window) - .map(w -> checkEndpoint(w, name, "end")) - .ifPresent(reporter::add); } - private String checkEndpoint(Window window, String name, String endpointType) { - boolean hasValid = Objects.nonNull(window.start) && Objects.nonNull(window.start.days < 0 ? window.start.days : window.start.timeUnitValue) && window.start.days < 0 ? window.start.days < 0 : window.start.timeUnitValue < 0; - return Optional.of(window) - .filter(w -> hasValid) - .map(w -> String.format(NEGATIVE_VALUE_ERROR, name, getEndpointValue(w.start), endpointType)) - .orElse(null); - } - private Object getEndpointValue(Window.Endpoint endpoint) { - return Objects.nonNull(endpoint.days) ? endpoint.days : endpoint.timeUnitValue; + private void checkAndReportIfNegative(Window.Endpoint windowDetails, WarningReporter reporter, String name, String type) { + if (Objects.nonNull(windowDetails) && Objects.nonNull(windowDetails.days) && windowDetails.days < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, windowDetails.days, type); + } else if (Objects.nonNull(windowDetails) && Objects.nonNull(windowDetails.timeUnitValue) && windowDetails.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, windowDetails.timeUnitValue, type); + } } + private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { if (Objects.nonNull(filter)) { if (filter.priorDays < 0) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java index 561bf9b7..1ff6c6f3 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java @@ -102,7 +102,7 @@ private Integer startDays(Window window) { .orElse(0)); }) .orElse(0); -} + } class TimeWindowInfo { private String name; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 690d3675..e22b520a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -352,9 +352,10 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ if(!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && expression.collapseSettings.eraPad != 0){ resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", "day"); }else{ + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); - resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -574,7 +575,7 @@ else if (startWindow.end.timeUnitValue != null) { clauses.add(String.format("%s <= %s", startEventDateExpression, endExpression)); } - // EndWindow + // EndWindow Window endWindow = criteria.endWindow; if (endWindow != null) { @@ -584,26 +585,26 @@ else if (startWindow.end.timeUnitValue != null) { if (endWindow.start.days != null) { startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); } else if (endWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); - } else { - startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; - } + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); + } else { + startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + } - if (startExpression != null) { - clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); - } + if (startExpression != null) { + clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); + } if (endWindow.end.days != null) { endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); } else if (endWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); - } else { - endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; - } + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); + } else { + endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + } - if (endExpression != null) { - clauses.add(String.format("%s <= %s", endEventDateExpression, endExpression)); - } + if (endExpression != null) { + clauses.add(String.format("%s <= %s", endEventDateExpression, endExpression)); + } } // RestrictVisit @@ -617,7 +618,7 @@ else if (startWindow.end.timeUnitValue != null) { return query; } - public String getWindowedCriteriaQuery(WindowedCriteria criteria, String eventTable) { + public String getWindowedCriteriaQuery(WindowedCriteria criteria, String eventTable) { String query = getWindowedCriteriaQuery(WINDOWED_CRITERIA_TEMPLATE, criteria, eventTable, null); return query; } @@ -775,13 +776,15 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - if(strat.offset != 0){ - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + if(strat.offset != 0){ + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); }else { strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); + return strategySql; } @@ -798,8 +801,18 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { } String strategySql = StringUtils.replace(CUSTOM_ERA_STRATEGY_TEMPLATE, "@eventTable", eventTable); strategySql = StringUtils.replace(strategySql, "@drugCodesetId", strat.drugCodesetId.toString()); - strategySql = StringUtils.replace(strategySql, "@gapDays", Integer.toString(strat.gapDays)); - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + if (IntervalUnit.DAY.getName().equals(strat.gapUnit)) { + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); + } else { + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); + } + strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); + if (IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + } else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + } + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); strategySql = StringUtils.replace(strategySql, "@drugExposureEndDateExpression", drugExposureEndDateExpression); return strategySql; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 26170865..c9d44214 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -18,16 +18,18 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public class CollapseSettings { @JsonProperty("CollapseType") public CollapseType collapseType = CollapseType.ERA; @JsonProperty("EraPad") - public int eraPad = 0; + public int eraPad = 0; @JsonProperty("EraPadUnit") - public String eraPadUnit; + public String eraPadUnit = "day"; @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java index f585e73d..aa307330 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java @@ -31,9 +31,17 @@ public class CustomEraStrategy extends EndStrategy { @JsonProperty("GapDays") public int gapDays = 0; + @JsonProperty("GapUnit") + public String gapUnit = "day"; + @JsonProperty("GapUnitValue") + public int gapUnitValue = 0; @JsonProperty("Offset") public int offset = 0; + @JsonProperty("OffsetUnit") + public String offsetUnit = "day"; + @JsonProperty("OffsetUnitValue") + public int offsetUnitValue = 0; @JsonProperty("DaysSupplyOverride") public Integer daysSupplyOverride = null; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index f2d0ed6b..7e5e27ba 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -18,12 +18,14 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author Chris Knoll */ +@JsonIgnoreProperties(ignoreUnknown = true) public class DateOffsetStrategy extends EndStrategy { public enum DateField { @@ -38,7 +40,7 @@ public enum DateField { @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; @JsonProperty("OffsetUnit") - public String offsetUnit; + public String offsetUnit = "day"; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java index 2a40b9cb..e55b58e7 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -3,6 +3,7 @@ import jdk.nashorn.internal.objects.annotations.Getter; public enum IntervalUnit { + DAY("day"), HOUR("hour"), MINUTE("minute"), SECOND("second"); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index e746eb50..22be0794 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -18,19 +18,25 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author cknoll1 */ +@JsonIgnoreProperties(ignoreUnknown = true) public class Window { public static class Endpoint { + + @JsonProperty("Days") + public Integer days; @JsonProperty("TimeUnitValue") public Integer timeUnitValue; @JsonProperty("TimeUnit") - public String timeUnit = IntervalUnit.DAY.getName(); + public String timeUnit="day"; + @JsonProperty("Coeff") public int coeff; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 8ca6c2a5..1a7b1b61 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -94,7 +94,8 @@ protected List resolveSelectClauses(T criteria) { } // dateAdjustment or default start/end dates if (criteria.dateAdjustment != null) { - selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, + selectCols.add( + BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))")); } else { diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index 29d2bf84..e2ef3ed9 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -279,46 +279,14 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi <#macro CountCriteria countCriteria level=0 indexLabel="cohort entry">having <#if countCriteria.occurrence.type == 0 && countCriteria.occurrence.count == 0>no<#else>${inputTypes.getCountType(countCriteria)} ${countCriteria.occurrence.count}<#-- --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> -<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]> - <#if countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??> - <#local temp> - <@inputTypes.Window countCriteria.startWindow indexLabel /> - - <#local windowParts+=[temp]> - - <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> -<#local temp> - <@inputTypes.Window countCriteria.endWindow indexLabel /> - - <#local windowParts+=[temp]> - - <#if countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??> - <#local temp> - <@inputTypes.Window countCriteria.startWindow indexLabel /> - - <#local windowParts+=[temp]> - - <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> - <#local temp> - <@inputTypes.Window countCriteria.endWindow indexLabel /> - - <#local windowParts+=[temp]> - -<#if countCriteria.restrictVisit!false> - <#local temp>at same visit as ${indexLabel} - - <#local restrictParts+=[temp]> - -<#if countCriteria.ignoreObservationPeriod!false> - <#local temp>allow events outside observation period - <#local restrictParts+=[temp]> - - <#if windowParts?size gt 0>${windowParts?join(" and ")} - <#if restrictParts?size gt 0>; - - <#if restrictParts?size gt 0>${restrictParts?join(" and ")} - - +<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> + <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> + <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> + <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if +windowParts?size gt 0>${windowParts?join(" and ")}<#if restrictParts?size gt 0>; <#if +restrictParts?size gt 0>${restrictParts?join(" and ")} <#-- Demographic Criteria --> <#macro DemographicCriteria c level=0 indexLabel = "cohort entry"><#local attrs = []><#local temp><@AgeGenderCriteria ageAtStart=c.age!{} gender=c.gender!{} /><#if temp?has_content><#local attrs+=[temp]><#local diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 15e16a34..1fad6ffb 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -24,12 +24,7 @@ The person exits the cohort at the end of continuous observation. <#assign dateOffsetFieldOptions = [{"id": "StartDate", "name": "start date"}, {"id": "EndDate", "name": "end date"}]> <#macro DateOffsetStrategy s> - <#if s.offset?? && s.offset == "day" > - The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offset "day"/>. - <#else > - The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. - - + The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. <#macro CustomEraStrategy s> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 3ab55c30..57887ad1 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -11,13 +11,13 @@ END Note!!!! <#import "./utils.ftl" as utils> <#-- DateRange --> <#assign dateRangeOptions = [ - {"id": "lt", "name": "before"}, - {"id": "lte", "name": "on or Before"}, - {"id": "eq", "name": "on"}, - {"id": "gt", "name": "after"}, - {"id": "gte", "name": "on or after"}, - {"id": "bt","name": "between"}, - {"id": "!bt", "name": "not between"} +{"id": "lt", "name": "before"}, +{"id": "lte", "name": "on or Before"}, +{"id": "eq", "name": "on"}, +{"id": "gt", "name": "after"}, +{"id": "gte", "name": "on or after"}, +{"id": "bt","name": "between"}, +{"id": "!bt", "name": "not between"} ]> <#macro DateRange range>${utils.optionName(dateRangeOptions, range.op)} <#-- @@ -25,101 +25,79 @@ END Note!!!! --><#if range.op?ends_with("bt")> and <#if range.extent?has_content>${utils.formatDate(range.extent)}<#else>_empty_ <#-- ConceptList --> -<#macro ConceptList list quote="\""><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if item?counter gt 1><#if - item?counter == list?size> or <#else>, ${item} +<#macro ConceptList list quote="\""><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if item?counter gt 1><#if +item?counter == list?size> or <#else>, ${item} <#-- ConceptSetSelection --> <#macro ConceptSetSelection selection defaultName="any"><#if selection.isExcluded!false>not in ${utils.codesetName(selection.codesetId!"", defaultName)} <#-- NumericRange --> <#assign numericRangeOptions = [ - {"id": "lt", "name": "<"}, - {"id": "lte", "name": "<="}, - {"id": "eq", "name": "="}, - {"id": "gt", "name": ">"}, - {"id": "gte", "name": ">="}, - {"id": "bt", "name": "between"}, - {"id": "!bt", "name": "not Between"} +{"id": "lt", "name": "<"}, +{"id": "lte", "name": "<="}, +{"id": "eq", "name": "="}, +{"id": "gt", "name": ">"}, +{"id": "gte", "name": ">="}, +{"id": "bt", "name": "between"}, +{"id": "!bt", "name": "not Between"} ]> <#macro NumericRange range>${utils.optionName(numericRangeOptions, range.op)} ${range.value!""}<#if range.op?ends_with("bt")> and ${range.extent!""} <#-- TextFilter --> <#assign textFilterOptions = [ - {"id": 'startsWith', "name": 'starting with'}, - {"id": 'contains', "name": 'containing'}, - {"id": 'endsWith', "name": 'ending with'}, - {"id": '!startsWith', "name": 'not starting with'}, - {"id": '!contains',"name": 'not containing'}, - {"id": '!endsWith',"name": 'not ending with'} +{"id": 'startsWith', "name": 'starting with'}, +{"id": 'contains', "name": 'containing'}, +{"id": 'endsWith', "name": 'ending with'}, +{"id": '!startsWith', "name": 'not starting with'}, +{"id": '!contains',"name": 'not containing'}, +{"id": '!endsWith',"name": 'not ending with'} ]/> <#macro TextFilter filter>${utils.optionName(textFilterOptions, filter.op)} "${filter.text!""}" <#-- Limits --> <#assign resultLimitOptions = [ - {"id": "All", "name": "all events"}, - {"id": "First", "name": "earliest event"}, - {"id": "Last", "name": "latest event"} +{"id": "All", "name": "all events"}, +{"id": "First", "name": "earliest event"}, +{"id": "Last", "name": "latest event"} ]/> <#macro Limit limit>${utils.optionName(resultLimitOptions, limit.type)} <#-- Group --> <#assign groupTypeOptions = [ - {"id": 'ALL', "name": 'all'}, - {"id": 'ANY', "name": 'any'}, - {"id": 'AT_LEAST', "name": 'at least'}, - {"id": 'AT_MOST', "name": 'at most'} +{"id": 'ALL', "name": 'all'}, +{"id": 'ANY', "name": 'any'}, +{"id": 'AT_LEAST', "name": 'at least'}, +{"id": 'AT_MOST', "name": 'at most'} ]> <#-- Count (with Window) --> <#assign countTypeOptions = [ - {"id": 1, "name": 'at most'}, - {"id": 0, "name": 'exactly'}, - {"id": 2, "name": 'at least'} +{"id": 1, "name": 'at most'}, +{"id": 0, "name": 'exactly'}, +{"id": 2, "name": 'at least'} ]> <#function getCountType(countCriteria)> - <#return utils.optionName(countTypeOptions, countCriteria.occurrence.type)> + <#return utils.optionName(countTypeOptions, countCriteria.occurrence.type)> <#function whichEventPart useEnd><#if useEnd><#return "ending"><#else><#return "starting"> <#function whichIndexPart useEnd><#if useEnd><#return "end date"><#else><#return "start date"> <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> -<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} -<#----> -<#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 > - anytime on or before ${indexLabel} - ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1> -<#if w.start.days??>in the ${w.start.days} days -<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} - +<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- +--><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} <#-- User Defined Period --> -<#macro UserDefinedPeriod p><#if +<#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if -p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} - - +p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index c606ec0f..f70ad927 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -26,7 +26,7 @@ INTO #strategy_ends from @eventTable et JOIN ( - select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(day,@offset, ENDS.era_end_date) as era_end_date + select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(@offsetUnit, @offsetUnitValue, ENDS.era_end_date) as era_end_date from ( select de.person_id, de.drug_exposure_start_date, MIN(e.END_DATE) as era_end_date @@ -34,7 +34,7 @@ JOIN JOIN ( --cteEndDates - select PERSON_ID, DATEADD(day,-1 * @gapDays,EVENT_DATE) as END_DATE -- unpad the end date by @gapDays + select PERSON_ID, DATEADD(@gapUnit,-1 * @gapUnitValue,EVENT_DATE) as END_DATE -- unpad the end date by @gapDays FROM ( select PERSON_ID, EVENT_DATE, EVENT_TYPE, @@ -49,7 +49,7 @@ JOIN UNION ALL -- add the end dates with NULL as the row number, padding the end dates by @gapDays to allow a grace period for overlapping ranges. - select PERSON_ID, DATEADD(day,@gapDays,DRUG_EXPOSURE_END_DATE), 1 as EVENT_TYPE, NULL + select PERSON_ID, DATEADD(@gapUnit,@gapUnitValue,DRUG_EXPOSURE_END_DATE), 1 as EVENT_TYPE, NULL FROM #drugTarget D ) RAWDATA ) E diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index cac7c35f..b79289dd 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,20 +1,9 @@ -- date offset strategy -SELECT - event_id, - person_id, - CASE - WHEN @offset IS NOT NULL - THEN - CASE - WHEN DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date THEN op_end_date - ELSE DATEADD(@offsetUnit, @offsetUnitValue, @dateField) - END - ELSE - CASE - WHEN DATEADD(day, @offset, @dateField) > op_end_date THEN op_end_date - ELSE DATEADD(day, @offset, @dateField) - END - END AS end_date +select event_id, + person_id, + case + when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date + else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date INTO #strategy_ends -FROM @eventTable; +from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 8fad4578..b2d1145b 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -63,13 +63,7 @@ from ( --cteEnds JOIN ( -- cteEndDates SELECT person_id, - CASE - WHEN - @eraconstructorpad IS NOT NULL - THEN - DATEADD(day, -1 * @eraconstructorpad, event_date) - ELSE - DATEADD(@era_pad_unit, -1 * @era_pad, event_date) END AS end_date + DATEADD(@era_pad_unit, -1 * @eraconstructorpad, event_date) as end_date FROM ( SELECT @@ -87,10 +81,9 @@ from ( --cteEnds UNION ALL - SELECT person_id - , DATEADD(@era_pad_unit, @era_pad, end_date) as end_date + , DATEADD(@era_pad_unit, -1 * @eraconstructorpad, end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 2f4fc4c5..0bafd06c 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -113,23 +113,94 @@ public void compareTo() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.timeUnitValue = 10; - window.start.timeUnit = IntervalUnit.DAY.getName(); + window.start.days = 10; + window.start.timeUnitValue = null; + window.start.timeUnit = null; window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.timeUnitValue = 5; - window.end.timeUnit = IntervalUnit.DAY.getName(); + window.end.days = 5; + window.end.timeUnitValue = null; + window.end.timeUnit = null; window.end.coeff = 1; assertEquals(0, Comparisons.compareTo(filter, window)); - window.start.timeUnitValue = 5; - window.start.timeUnit = IntervalUnit.DAY.getName(); - window.end.timeUnitValue = 5; - window.end.timeUnit = IntervalUnit.DAY.getName(); - assertEquals(432000, Comparisons.compareTo(filter, window)); + window.start.days = 5; + window.end.days = 5; + + window.start.timeUnitValue = null; + window.start.timeUnit = null; + window.end.timeUnitValue = null; + window.end.timeUnit = null; + + assertEquals(5, Comparisons.compareTo(filter, window)); + } + + @Test + public void compareToHours() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.days = 0; + window.start.timeUnit = "hour"; + window.start.timeUnitValue = 72; + window.start.coeff = -1; + + window.end.days = 0; + window.end.timeUnit = "hour"; + window.end.timeUnitValue = 120; + window.end.coeff = 1; + + assertEquals(604800, Comparisons.compareTo(filter, window)); + } + @Test + public void compareToMinute() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.days = 0; + window.start.timeUnit = "minute"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.days = 0; + window.end.timeUnit = "minute"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + + assertEquals(1289700, Comparisons.compareTo(filter, window)); + } + + @Test + public void compareToSecond() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.days = 0; + window.start.timeUnit = "second"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.days = 0; + window.end.timeUnit = "second"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + + assertEquals(1295895, Comparisons.compareTo(filter, window)); } @Test @@ -243,10 +314,12 @@ public void compareCriteria() { public void isBefore() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.timeUnitValue = 1; + window.start.days = 1; + window.start.timeUnitValue = null; window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.timeUnitValue = 1; + window.end.days = 1; + window.end.timeUnitValue = null; window.end.coeff = 1; assertEquals(false, Comparisons.isBefore(window)); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index c20ca187..83f14077 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -29,7 +29,8 @@ public static Window getPrior365Window() { // index starts between 365d before Window.Endpoint startPoint = new Window.Endpoint(); startPoint.coeff = -1; - startPoint.timeUnitValue = 365; + startPoint.days = 365; + startPoint.timeUnitValue = null; prior365Window.start = startPoint; // ... and 0 days before Window.Endpoint endPoint = new Window.Endpoint(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index dd384912..c40cd736 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -625,20 +625,20 @@ public void nullCodesetIdTest() { public void nullCohortTest() { exceptionRule.expect(RuntimeException.class); pf.renderCohort((CohortExpression)null); - + } @Test public void nullConceptSetTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSet((ConceptSet)null); - + } @Test public void nullConceptSetListTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSetList((ConceptSet[])null); - + } } diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json index 13b41373..3e1ea072 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json @@ -539,8 +539,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 1 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json index be6f11f6..266cc3e9 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json @@ -309,8 +309,9 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPadUnit": "hour", + "EraPadUnitValue": 0, + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueCorrect.json b/src/test/resources/checkers/censoringEventCheckValueCorrect.json index 8b517a9e..7df72672 100644 --- a/src/test/resources/checkers/censoringEventCheckValueCorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueCorrect.json @@ -408,8 +408,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json index e791905b..515aca1b 100644 --- a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json @@ -288,8 +288,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json index 9c9c05e3..34afceb4 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json @@ -115,8 +115,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json index af967543..e0d400d5 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json @@ -89,8 +89,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json index e64e1c55..7f224759 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json @@ -116,8 +116,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json index 92b71212..3153dd0c 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json @@ -116,8 +116,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json index f51ad3d1..16f05ebc 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json @@ -39,8 +39,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -84,8 +83,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -106,8 +104,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -134,8 +131,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json index 2b593bac..ad419909 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json @@ -39,8 +39,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -84,8 +83,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -106,8 +104,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -134,8 +131,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckCorrect.json b/src/test/resources/checkers/domainTypeCheckCorrect.json index 678b7562..dc401077 100644 --- a/src/test/resources/checkers/domainTypeCheckCorrect.json +++ b/src/test/resources/checkers/domainTypeCheckCorrect.json @@ -217,8 +217,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckIncorrect.json b/src/test/resources/checkers/domainTypeCheckIncorrect.json index 9c9c05e3..34afceb4 100644 --- a/src/test/resources/checkers/domainTypeCheckIncorrect.json +++ b/src/test/resources/checkers/domainTypeCheckIncorrect.json @@ -115,8 +115,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckCorrect.json b/src/test/resources/checkers/drugDomainCheckCorrect.json index 0a070252..44f2dfae 100644 --- a/src/test/resources/checkers/drugDomainCheckCorrect.json +++ b/src/test/resources/checkers/drugDomainCheckCorrect.json @@ -162,14 +162,17 @@ "CustomEra": { "DrugCodesetId": 2, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckIncorrect.json b/src/test/resources/checkers/drugDomainCheckIncorrect.json index 64a99002..65259d56 100644 --- a/src/test/resources/checkers/drugDomainCheckIncorrect.json +++ b/src/test/resources/checkers/drugDomainCheckIncorrect.json @@ -162,14 +162,17 @@ "CustomEra": { "DrugCodesetId": 1, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugEraCheckCorrect.json b/src/test/resources/checkers/drugEraCheckCorrect.json index 565aad14..047e2f50 100644 --- a/src/test/resources/checkers/drugEraCheckCorrect.json +++ b/src/test/resources/checkers/drugEraCheckCorrect.json @@ -53,13 +53,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 0, - "TimeUnit": "day", + "Days": 0, "Coeff": -1 }, "End": { - "TimeUnitValue": 0, - "TimeUnit": "day", + "Days": 0, "Coeff": -1 }, "UseEventEnd": true @@ -78,8 +76,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json index 4b4cdbe3..4ad7d1c3 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json @@ -50,8 +50,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json index 1741beb8..da030ecf 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json @@ -73,8 +73,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json index b2d56fae..6c79f167 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json @@ -185,8 +185,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json index 05b5b93e..e726c78c 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json @@ -532,8 +532,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/emptyCensoringCriteriaList.json b/src/test/resources/checkers/emptyCensoringCriteriaList.json index 6dec6bdc..ff98150d 100644 --- a/src/test/resources/checkers/emptyCensoringCriteriaList.json +++ b/src/test/resources/checkers/emptyCensoringCriteriaList.json @@ -49,8 +49,7 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyCorrelatedCriteria.json b/src/test/resources/checkers/emptyCorrelatedCriteria.json index dbe91807..2b70c327 100644 --- a/src/test/resources/checkers/emptyCorrelatedCriteria.json +++ b/src/test/resources/checkers/emptyCorrelatedCriteria.json @@ -148,8 +148,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckCorrect.json b/src/test/resources/checkers/emptyDemographicCheckCorrect.json index 34a612c0..8d3cc58c 100644 --- a/src/test/resources/checkers/emptyDemographicCheckCorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckCorrect.json @@ -456,14 +456,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 30, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 30, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json index 9ac8d92f..58428c18 100644 --- a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json @@ -452,14 +452,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 30, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 30, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyInclusionRules.json b/src/test/resources/checkers/emptyInclusionRules.json index e0af1736..d7def9e6 100644 --- a/src/test/resources/checkers/emptyInclusionRules.json +++ b/src/test/resources/checkers/emptyInclusionRules.json @@ -54,8 +54,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyPrimaryCriteriaList.json b/src/test/resources/checkers/emptyPrimaryCriteriaList.json index acbd8ac3..c4c7425d 100644 --- a/src/test/resources/checkers/emptyPrimaryCriteriaList.json +++ b/src/test/resources/checkers/emptyPrimaryCriteriaList.json @@ -39,8 +39,7 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckCorrect.json b/src/test/resources/checkers/eventsProgressionCheckCorrect.json index 0cf117c6..5ebed29c 100644 --- a/src/test/resources/checkers/eventsProgressionCheckCorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckCorrect.json @@ -76,14 +76,17 @@ "CustomEra": { "DrugCodesetId": 2, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json index 5d927fb3..8cdbb5a2 100644 --- a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json @@ -76,14 +76,17 @@ "CustomEra": { "DrugCodesetId": 2, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json index 270f534d..121726e5 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json @@ -478,8 +478,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json index afdc0ef0..50a9a34f 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json @@ -268,8 +268,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/noExitCriteriaCheck.json b/src/test/resources/checkers/noExitCriteriaCheck.json index 822578ab..5cf211ff 100644 --- a/src/test/resources/checkers/noExitCriteriaCheck.json +++ b/src/test/resources/checkers/noExitCriteriaCheck.json @@ -77,9 +77,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" -} +} \ No newline at end of file diff --git a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json index 913dc418..3b63bd3b 100644 --- a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json +++ b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json @@ -77,8 +77,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json index 79152e6b..145d690a 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json @@ -1358,8 +1358,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json index e1a3dd8b..24864bf0 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json @@ -701,8 +701,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/timePatternCheckCorrect.json b/src/test/resources/checkers/timePatternCheckCorrect.json index 5b80ee70..75146327 100644 --- a/src/test/resources/checkers/timePatternCheckCorrect.json +++ b/src/test/resources/checkers/timePatternCheckCorrect.json @@ -59,8 +59,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -81,8 +80,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -103,8 +101,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -126,8 +123,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/timePatternCheckIncorrect.json b/src/test/resources/checkers/timePatternCheckIncorrect.json index 1488cbe4..964b92ad 100644 --- a/src/test/resources/checkers/timePatternCheckIncorrect.json +++ b/src/test/resources/checkers/timePatternCheckIncorrect.json @@ -88,8 +88,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "End": { @@ -110,8 +109,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "End": { @@ -132,8 +130,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -155,8 +152,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -177,8 +173,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -199,8 +194,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -222,8 +216,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/unusedConceptSet.json b/src/test/resources/checkers/unusedConceptSet.json index b37b5ee4..3cc898be 100644 --- a/src/test/resources/checkers/unusedConceptSet.json +++ b/src/test/resources/checkers/unusedConceptSet.json @@ -4433,8 +4433,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/unusedConceptSetCorrect.json b/src/test/resources/checkers/unusedConceptSetCorrect.json index 78dae501..38143407 100644 --- a/src/test/resources/checkers/unusedConceptSetCorrect.json +++ b/src/test/resources/checkers/unusedConceptSetCorrect.json @@ -4826,8 +4826,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json index 35d19f07..a6cf2e0e 100644 --- a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json +++ b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json @@ -287,13 +287,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -390,13 +388,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -512,13 +508,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -690,13 +684,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -803,13 +795,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1048,13 +1038,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1271,13 +1259,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1477,13 +1463,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1587,13 +1571,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1767,13 +1749,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1950,13 +1930,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -2114,13 +2092,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -2149,13 +2125,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 100, - "TimeUnit": "day", + "Days": 100, "Coeff": -1 }, "End": { - "TimeUnitValue": 100, - "TimeUnit": "day", + "Days": 100, "Coeff": 1 }, "UseEventEnd": false @@ -2179,13 +2153,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 60, - "TimeUnit": "day", + "Days": 60, "Coeff": -1 }, "End": { - "TimeUnitValue": 60, - "TimeUnit": "day", + "Days": 60, "Coeff": 1 }, "UseEventEnd": false @@ -2203,13 +2175,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -2230,9 +2200,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "minute", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" -} +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json index 352fd00d..cd76dbba 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json @@ -30,7 +30,7 @@ "ConditionOccurrence": { "CodesetId": 0 } - } + } ], "ObservationWindow": { "PriorDays": 0, @@ -54,8 +54,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -79,6 +80,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json index e2a4ee39..1b6f524d 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json @@ -51,13 +51,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -75,13 +77,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -105,11 +109,13 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -129,11 +135,13 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -161,6 +169,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json index b31aa4e6..99e17ac8 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json @@ -52,16 +52,18 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -88,6 +90,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 8b983658..9c3b8584 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -23,6 +23,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 0, "OffsetUnit": "hour", "OffsetUnitValue": 0 } @@ -30,6 +31,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 30, "EraPadUnit": "minute", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json index 4d640023..20615fa7 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json @@ -4,19 +4,7 @@ "cohort_definition_id":1, "subject_id":1, "cohort_start_date":"2009-06-16", - "cohort_end_date":"2009-06-19" - }, - { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2009-07-01", - "cohort_end_date":"2009-07-04" - }, - { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2009-07-27", - "cohort_end_date":"2009-07-30" + "cohort_end_date":"2009-06-27" } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/censorEventExpression.json b/src/test/resources/cohortgeneration/exits/censorEventExpression.json index 725f718c..72b8d5cc 100644 --- a/src/test/resources/cohortgeneration/exits/censorEventExpression.json +++ b/src/test/resources/cohortgeneration/exits/censorEventExpression.json @@ -50,6 +50,7 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", + "Offset": 31, "OffsetUnit": "day", "OffsetUnitValue": 31 } @@ -61,6 +62,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json index 09efd2fe..2fb0db21 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json @@ -51,12 +51,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 60, - "Offset": 9999 + "GapUnit": "day", + "GapUnitValue": 60, + "Offset": 9999, + "OffsetUnit": "day", + "OffsetUnitValue": 9999 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index b142ef89..bd12cbe7 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -51,12 +51,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 60, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 60, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json index 7faca602..542a8829 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json @@ -50,6 +50,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 99999, "OffsetUnit": "day", "OffsetUnitValue": 99999 } @@ -57,6 +58,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json index 0f5ab2e2..74fa1daf 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json @@ -50,6 +50,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 31, "OffsetUnit": "day", "OffsetUnitValue": 31 } @@ -57,6 +58,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json index e66c806f..0713b0dc 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json @@ -44,6 +44,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json index 200fe127..3580bf44 100644 --- a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json +++ b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json @@ -86,6 +86,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -106,6 +107,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/limits/limitExpression.json b/src/test/resources/cohortgeneration/limits/limitExpression.json index 23ae40b8..b638424b 100644 --- a/src/test/resources/cohortgeneration/limits/limitExpression.json +++ b/src/test/resources/cohortgeneration/limits/limitExpression.json @@ -54,8 +54,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -79,6 +80,7 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", + "Offset": 1, "OffsetUnit": "day", "OffsetUnitValue": 1 } @@ -86,6 +88,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 60, "EraPadUnit": "day", "EraPadUnitValue": 60 }, diff --git a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json index eb27b963..4f3fb3b6 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json @@ -4,37 +4,13 @@ "cohort_definition_id": 1, "subject_id": 1, "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-02-02" + "cohort_end_date": "2000-06-02" }, { "cohort_definition_id": 1, "subject_id": 1, - "cohort_start_date": "2000-06-01", - "cohort_end_date": "2000-08-02" - }, - { - "cohort_definition_id": 1, - "subject_id": 1, - "cohort_start_date": "2000-11-01", - "cohort_end_date": "2000-12-02" - }, - { - "cohort_definition_id": 2, - "subject_id": 1, - "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-02-02" - }, - { - "cohort_definition_id": 3, - "subject_id": 1, - "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-02-02" - }, - { - "cohort_definition_id": 5, - "subject_id": 1, - "cohort_start_date": "2000-12-01", - "cohort_end_date": "2000-12-02" + "cohort_start_date": "2000-07-01", + "cohort_end_date": "2000-10-02" } ] } \ No newline at end of file diff --git a/src/test/resources/printfriendly/allAttributes.json b/src/test/resources/printfriendly/allAttributes.json index 1c5573d4..97a4241f 100644 --- a/src/test/resources/printfriendly/allAttributes.json +++ b/src/test/resources/printfriendly/allAttributes.json @@ -70,21 +70,24 @@ }, "StartWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -144,8 +147,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -177,8 +181,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -323,8 +328,9 @@ "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -357,8 +363,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -380,8 +387,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -462,8 +470,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -552,8 +561,9 @@ "Coeff": -1 }, "End": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -575,8 +585,9 @@ "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -683,11 +694,13 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -724,6 +737,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -810,7 +824,7 @@ }, "End": { "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -831,6 +845,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/anyCondition.json b/src/test/resources/printfriendly/anyCondition.json index 73ef7919..89ed96ca 100644 --- a/src/test/resources/printfriendly/anyCondition.json +++ b/src/test/resources/printfriendly/anyCondition.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/censorCriteria.json b/src/test/resources/printfriendly/censorCriteria.json index e2948c49..8f69c475 100644 --- a/src/test/resources/printfriendly/censorCriteria.json +++ b/src/test/resources/printfriendly/censorCriteria.json @@ -324,13 +324,15 @@ }, "StartWindow": { "Start": { + "Days": 365, "TimeUnitValue": 365, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 60, "TimeUnitValue": 60, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -358,6 +360,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/conceptSet_simple.json b/src/test/resources/printfriendly/conceptSet_simple.json index de95aee1..09cd48cf 100644 --- a/src/test/resources/printfriendly/conceptSet_simple.json +++ b/src/test/resources/printfriendly/conceptSet_simple.json @@ -74,6 +74,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/conditionEra.json b/src/test/resources/printfriendly/conditionEra.json index 9c7865f5..8ba4378d 100644 --- a/src/test/resources/printfriendly/conditionEra.json +++ b/src/test/resources/printfriendly/conditionEra.json @@ -37,26 +37,30 @@ }, "StartWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false }, "EndWindow": { "Start": { + "Days": 7, "TimeUnitValue": 7, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": true @@ -140,11 +144,13 @@ }, "StartWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -165,6 +171,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/conditionOccurrence.json b/src/test/resources/printfriendly/conditionOccurrence.json index 7cd95ca8..97f57cc9 100644 --- a/src/test/resources/printfriendly/conditionOccurrence.json +++ b/src/test/resources/printfriendly/conditionOccurrence.json @@ -52,8 +52,9 @@ }, "StartWindow": { "Start": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -205,8 +206,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": true @@ -244,6 +246,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": 1 diff --git a/src/test/resources/printfriendly/continuousObservation_none.json b/src/test/resources/printfriendly/continuousObservation_none.json index 9d63f1b4..354fd870 100644 --- a/src/test/resources/printfriendly/continuousObservation_none.json +++ b/src/test/resources/printfriendly/continuousObservation_none.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/continuousObservation_post.json b/src/test/resources/printfriendly/continuousObservation_post.json index 05538e32..9587c373 100644 --- a/src/test/resources/printfriendly/continuousObservation_post.json +++ b/src/test/resources/printfriendly/continuousObservation_post.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/continuousObservation_prior.json b/src/test/resources/printfriendly/continuousObservation_prior.json index 774f4a73..dc43eebe 100644 --- a/src/test/resources/printfriendly/continuousObservation_prior.json +++ b/src/test/resources/printfriendly/continuousObservation_prior.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/continuousObservation_priorpost.json b/src/test/resources/printfriendly/continuousObservation_priorpost.json index 56abf230..6cf78eaa 100644 --- a/src/test/resources/printfriendly/continuousObservation_priorpost.json +++ b/src/test/resources/printfriendly/continuousObservation_priorpost.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/countCriteria.json b/src/test/resources/printfriendly/countCriteria.json index d5561bd3..700ea4b8 100644 --- a/src/test/resources/printfriendly/countCriteria.json +++ b/src/test/resources/printfriendly/countCriteria.json @@ -30,13 +30,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -78,8 +80,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -100,8 +103,9 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -240,6 +244,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -272,6 +277,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -303,6 +309,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -337,6 +344,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -356,6 +364,7 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -384,13 +393,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -411,6 +422,7 @@ "Coeff": -1 }, "End": { + "Days": 31, "TimeUnitValue": 31, "TimeUnit": "day", "Coeff": -1 diff --git a/src/test/resources/printfriendly/countDistinctCriteria.json b/src/test/resources/printfriendly/countDistinctCriteria.json index 45bed9c7..f5dbe6d8 100644 --- a/src/test/resources/printfriendly/countDistinctCriteria.json +++ b/src/test/resources/printfriendly/countDistinctCriteria.json @@ -30,13 +30,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -79,8 +81,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -107,8 +110,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -131,8 +135,9 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -173,6 +178,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/customEraExit.json b/src/test/resources/printfriendly/customEraExit.json index e74f2963..fe2dca24 100644 --- a/src/test/resources/printfriendly/customEraExit.json +++ b/src/test/resources/printfriendly/customEraExit.json @@ -43,13 +43,18 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 14, + "GapUnit": "day", + "GapUnitValue": 14, "Offset": 1, + "OffsetUnit": "day", + "OffsetUnitValue": 1, "DaysSupplyOverride": 7 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/dateOffset.json b/src/test/resources/printfriendly/dateOffset.json index f52faaeb..e6a65744 100644 --- a/src/test/resources/printfriendly/dateOffset.json +++ b/src/test/resources/printfriendly/dateOffset.json @@ -42,6 +42,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 7, "OffsetUnit": "day", "OffsetUnitValue": 7 } @@ -49,6 +50,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/death.json b/src/test/resources/printfriendly/death.json index 4c232614..43c585a1 100644 --- a/src/test/resources/printfriendly/death.json +++ b/src/test/resources/printfriendly/death.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false diff --git a/src/test/resources/printfriendly/deviceExposure.json b/src/test/resources/printfriendly/deviceExposure.json index 3f6a4e10..96f9f895 100644 --- a/src/test/resources/printfriendly/deviceExposure.json +++ b/src/test/resources/printfriendly/deviceExposure.json @@ -66,8 +66,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -181,8 +182,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -217,11 +219,13 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, "TimeUnit": "day", "Coeff": 1 diff --git a/src/test/resources/printfriendly/doseEra.json b/src/test/resources/printfriendly/doseEra.json index 1c1493d4..7fb29479 100644 --- a/src/test/resources/printfriendly/doseEra.json +++ b/src/test/resources/printfriendly/doseEra.json @@ -38,13 +38,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -62,13 +64,14 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -172,13 +175,15 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -196,13 +201,15 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -240,6 +247,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -266,6 +274,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -285,6 +294,7 @@ ], "CensoringCriteria": [], "CollapseSettings": { + "EraPad": 0, "CollapseType": "ERA", "EraPadUnit": "day", "EraPadUnitValue": 0 diff --git a/src/test/resources/printfriendly/drugEra.json b/src/test/resources/printfriendly/drugEra.json index 6f8247bc..c46b4815 100644 --- a/src/test/resources/printfriendly/drugEra.json +++ b/src/test/resources/printfriendly/drugEra.json @@ -41,8 +41,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -159,6 +160,7 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -181,6 +183,7 @@ ], "CensoringCriteria": [], "CollapseSettings": { + "EraPad": 0, "CollapseType": "ERA", "EraPadUnit": "day", "EraPadUnitValue": 0 diff --git a/src/test/resources/printfriendly/drugExposure.json b/src/test/resources/printfriendly/drugExposure.json index b2ad53e5..7caadd91 100644 --- a/src/test/resources/printfriendly/drugExposure.json +++ b/src/test/resources/printfriendly/drugExposure.json @@ -250,6 +250,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/measurement.json b/src/test/resources/printfriendly/measurement.json index 0f5f0bc7..e2001a23 100644 --- a/src/test/resources/printfriendly/measurement.json +++ b/src/test/resources/printfriendly/measurement.json @@ -41,8 +41,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -60,8 +61,9 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -264,7 +266,8 @@ "CollapseSettings": { "CollapseType": "ERA", "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPadUnitValue": 0, + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/noCensorCriteria.json b/src/test/resources/printfriendly/noCensorCriteria.json index dd045db5..bfdd20d4 100644 --- a/src/test/resources/printfriendly/noCensorCriteria.json +++ b/src/test/resources/printfriendly/noCensorCriteria.json @@ -398,6 +398,7 @@ ], "CollapseSettings":{ "CollapseType":"ERA", + "EraPad":0, "EraPadUnit": "days", "EraPadUnitValue": 0 } diff --git a/src/test/resources/printfriendly/nullCodesetId.json b/src/test/resources/printfriendly/nullCodesetId.json index 03ec724a..81fb082f 100644 --- a/src/test/resources/printfriendly/nullCodesetId.json +++ b/src/test/resources/printfriendly/nullCodesetId.json @@ -376,6 +376,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/observation.json b/src/test/resources/printfriendly/observation.json index 561af4c1..ae536e6a 100644 --- a/src/test/resources/printfriendly/observation.json +++ b/src/test/resources/printfriendly/observation.json @@ -41,6 +41,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -217,6 +218,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/observationPeriod_1.json b/src/test/resources/printfriendly/observationPeriod_1.json index a55605eb..450124eb 100644 --- a/src/test/resources/printfriendly/observationPeriod_1.json +++ b/src/test/resources/printfriendly/observationPeriod_1.json @@ -35,6 +35,7 @@ }, "StartWindow": { "Start": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": 1 @@ -120,6 +121,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/procedureOccurrence.json b/src/test/resources/printfriendly/procedureOccurrence.json index 5118c603..f3881bf4 100644 --- a/src/test/resources/printfriendly/procedureOccurrence.json +++ b/src/test/resources/printfriendly/procedureOccurrence.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -191,6 +192,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/specimen.json b/src/test/resources/printfriendly/specimen.json index f577d458..dd35d186 100644 --- a/src/test/resources/printfriendly/specimen.json +++ b/src/test/resources/printfriendly/specimen.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -176,6 +177,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/visit.json b/src/test/resources/printfriendly/visit.json index a6296603..6660dda0 100644 --- a/src/test/resources/printfriendly/visit.json +++ b/src/test/resources/printfriendly/visit.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -177,6 +178,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json index 211b685c..20ba6752 100644 --- a/src/test/resources/printfriendly/visitDetail.json +++ b/src/test/resources/printfriendly/visitDetail.json @@ -40,6 +40,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -109,6 +110,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, From 07a7160311f8e6719379556b1463bc4214894073 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Mon, 8 Jan 2024 20:06:44 +0700 Subject: [PATCH 04/25] add sql test --- .../circe/check/checkers/Comparisons.java | 24 +- .../CohortExpressionQueryBuilder.java | 34 +- .../cohortdefinition/CollapseSettings.java | 2 +- .../cohortdefinition/DateOffsetStrategy.java | 11 +- .../circe/cohortdefinition/IntervalUnit.java | 2 - .../ohdsi/circe/cohortdefinition/Window.java | 25 +- .../printfriendly/cohortExpression.ftl | 2 +- .../printfriendly/criteriaTypes.ftl | 4 +- .../printfriendly/endStrategyTypes.ftl | 6 + .../printfriendly/inputTypes.ftl | 3 +- .../sql/customEraStrategy.sql | 2 +- .../circe/check/checkers/ComparisonsTest.java | 6 - .../checkers/CriteriaCheckValueTest.java | 46 +- .../checkers/DeathTimeWindowCheckTest.java | 8 +- .../CohortGeneration_5_0_0_Test.java | 77 ++- .../builders/CriteriaUtils.java | 18 + .../builders/WindowCriteria_5_0_0_Test.java | 16 + .../printfriendly/PrintFriendlyTest.java | 36 +- ...tionalCriteriaCheckValueCorrectMinute.json | 562 ++++++++++++++++++ ...additionalCriteriaCheckValueIncorrect.json | 3 +- ...tionalCriteriaCheckValueIncorrectHour.json | 329 ++++++++++ .../deathTimeWindowCheckCorrectSecond.json | 140 +++++ .../censorWindow/censorWindowExpression.json | 8 +- .../groupExpressionHour.json | 169 ++++++ .../eraDupes/eraDupesExpression.json | 2 +- .../continuousExposureCensorExpression.json | 1 - .../exits/continuousExposureExpression.json | 3 - .../exits/continuousExposure_PREP.json | 4 +- .../firstOccurrenceTestExpression.json | 1 - .../limits/limitExpression.json | 10 +- .../limits/limitTest_VERIFY.json | 40 +- .../printfriendly/countDistinctCriteria.json | 14 +- ...tDistinctCriteriaWithTimeUnitInterval.json | 181 ++++++ .../printfriendly/customEraExitHour.json | 60 ++ .../printfriendly/customEraExitMinute.json | 60 ++ .../printfriendly/customEraExitSecond.json | 60 ++ .../resources/printfriendly/visitDetail.json | 6 +- 37 files changed, 1842 insertions(+), 133 deletions(-) create mode 100644 src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json create mode 100644 src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json create mode 100644 src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json create mode 100644 src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json create mode 100644 src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json create mode 100644 src/test/resources/printfriendly/customEraExitHour.json create mode 100644 src/test/resources/printfriendly/customEraExitMinute.json create mode 100644 src/test/resources/printfriendly/customEraExitSecond.json diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 8bffe26e..8f54990c 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -107,19 +107,19 @@ public static Predicate compare(Concept source) { .build(); } - public static int compareTo(ObservationFilter filter, Window window) { - int range1, range2Start, range2End; - if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) && window.start.days != 0)) { - range1 = filter.postDays + filter.priorDays; - range2Start = window.start.coeff * window.start.days; - range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; - } else { - range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; - range2Start = getTimeInSeconds(window.start); - range2End = getTimeInSeconds(window.end); - } - return range1 - (range2End - range2Start); + public static int compareTo(ObservationFilter filter, Window window) { + int range1, range2Start, range2End; + if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) || IntervalUnit.DAY.getName().equals(window.start.timeUnit))) { + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + } else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); } + return range1 - (range2End - range2Start); + } private static int getTimeInSeconds(Window.Endpoint endpoint) { return Optional.ofNullable(endpoint) .map(ep -> { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index e22b520a..883b440a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -350,12 +350,12 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); - if(!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && expression.collapseSettings.eraPad != 0){ - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); - resultSql = StringUtils.replace(resultSql, "@era_pad_unit", "day"); - }else{ - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); - resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + if (!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && (expression.collapseSettings.eraPadUnit == null || IntervalUnit.DAY.getName().equals(expression.collapseSettings.eraPadUnit))) { + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + } else { + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -550,7 +550,7 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.days != null) { + if (startWindow.start.days != null && (startWindow.start.timeUnit == null || startWindow.start.timeUnit.equals(IntervalUnit.DAY.getName()))) { startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); } else if (startWindow.start.timeUnitValue != null) { startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); @@ -776,12 +776,12 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - if(strat.offset != 0){ - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); - strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); - }else { - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); - strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + if (strat.offsetUnit == null || strat.offsetUnit.equals(IntervalUnit.DAY.getName())) { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); + } else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); @@ -802,15 +802,15 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { String strategySql = StringUtils.replace(CUSTOM_ERA_STRATEGY_TEMPLATE, "@eventTable", eventTable); strategySql = StringUtils.replace(strategySql, "@drugCodesetId", strat.drugCodesetId.toString()); if (IntervalUnit.DAY.getName().equals(strat.gapUnit)) { - strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); } else { - strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); } strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); if (IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); } else { - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); } strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); strategySql = StringUtils.replace(strategySql, "@drugExposureEndDateExpression", drugExposureEndDateExpression); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index c9d44214..2fcfc1f5 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -29,7 +29,7 @@ public class CollapseSettings { @JsonProperty("EraPad") public int eraPad = 0; @JsonProperty("EraPadUnit") - public String eraPadUnit = "day"; + public String eraPadUnit = IntervalUnit.DAY.getName(); @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 7e5e27ba..476f2bce 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -22,21 +22,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * * @author Chris Knoll */ @JsonIgnoreProperties(ignoreUnknown = true) public class DateOffsetStrategy extends EndStrategy { - public enum DateField { - StartDate, - EndDate - } @JsonProperty("Offset") public int offset = 0; @JsonProperty("DateField") public DateField dateField = DateField.StartDate; - @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; @JsonProperty("OffsetUnit") @@ -46,4 +40,9 @@ public enum DateField { public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { return dispatcher.getStrategySql(this, eventTable); } + + public enum DateField { + StartDate, + EndDate + } } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java index e55b58e7..18da37c2 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -1,7 +1,5 @@ package org.ohdsi.circe.cohortdefinition; -import jdk.nashorn.internal.objects.annotations.Getter; - public enum IntervalUnit { DAY("day"), HOUR("hour"), diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index 22be0794..5667d448 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -22,12 +22,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * * @author cknoll1 */ @JsonIgnoreProperties(ignoreUnknown = true) public class Window { + @JsonProperty("Start") + public Endpoint start; + @JsonProperty("End") + public Endpoint end; + @JsonProperty("UseIndexEnd") + public Boolean useIndexEnd; + @JsonProperty("UseEventEnd") + public Boolean useEventEnd; + public static class Endpoint { @JsonProperty("Days") @@ -35,22 +43,9 @@ public static class Endpoint { @JsonProperty("TimeUnitValue") public Integer timeUnitValue; @JsonProperty("TimeUnit") - public String timeUnit="day"; + public String timeUnit = "day"; @JsonProperty("Coeff") public int coeff; } - - @JsonProperty("Start") - public Endpoint start; - - @JsonProperty("End") - public Endpoint end; - - @JsonProperty("UseIndexEnd") - public Boolean useIndexEnd; - - @JsonProperty("UseEventEnd") - public Boolean useEventEnd; - } diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl index 5a2f6e12..6389449a 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl @@ -64,7 +64,7 @@ ${cc?counter}. <@ct.Criteria c=cc/> ### Cohort Eras -<#if collapseSettings.eraPad?has_content && collapseSettings.eraPad != 0> +<#if !collapseSettings.eraPadUnit?? || collapseSettings.eraPadUnit == "day"> Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPad} days of each other. <#else > Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPadUnitValue} ${collapseSettings.eraPadUnit}s of each other. diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index e2ef3ed9..40eab53c 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -280,8 +280,8 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> <#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue?? || countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue?? || countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??)> <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 1fad6ffb..651883f8 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -28,7 +28,13 @@ The person exits the cohort at the end of continuous observation. <#macro CustomEraStrategy s> + <#if s.offsetUnit == "day"> The cohort end date will be based on a continuous exposure to ${utils.codesetName(s.drugCodesetId!"", "_invalid drug specified_")}: allowing ${s.gapDays} days between exposures, adding <@utils.formatValue s.offset "day"/> after exposure ends, and <#if s.daysSupplyOverride??>forcing drug exposure days supply to: <@utils.formatValue s.daysSupplyOverride "day"/>.<#else>using days supply and exposure end date for exposure duration. + <#else > +The cohort end date will be based on a continuous exposure to ${utils.codesetName(s.drugCodesetId!"", "_invalid drug specified_")}: +allowing ${s.gapUnitValue} ${s.offsetUnit} between exposures, adding <@utils.formatValue s.offsetUnitValue s.offsetUnit/> after exposure ends, and <#if +s.daysSupplyOverride??>forcing drug exposure ${s.offsetUnit} supply to: <@utils.formatValue s.daysSupplyOverride s.offsetUnit/>.<#else>using ${s.offsetUnit} supply and exposure end date for exposure duration. + diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 57887ad1..a8e04f79 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -90,10 +90,11 @@ item?counter == list?size> or <#else>, ${item} <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> <#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- ---><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#if ((!w.start.timeUnitValue?? && w.end.timeUnitValue?? && w.end.timeUnitValue == 0) || !w.start.days?? && w.end.days?? && w.end.days == 0) && w.start.coeff == -1>anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif w.start.days??>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} <#-- User Defined Period --> diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index f70ad927..3bde650c 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -26,7 +26,7 @@ INTO #strategy_ends from @eventTable et JOIN ( - select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(@offsetUnit, @offsetUnitValue, ENDS.era_end_date) as era_end_date + select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(day, @offsetUnitValue, ENDS.era_end_date) as era_end_date from ( select de.person_id, de.drug_exposure_start_date, MIN(e.END_DATE) as era_end_date diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 0bafd06c..4ddeb5e8 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -147,12 +147,10 @@ public void compareToHours() { Window window = new Window(); window.start = new Window.Endpoint(); window.end = new Window.Endpoint(); - window.start.days = 0; window.start.timeUnit = "hour"; window.start.timeUnitValue = 72; window.start.coeff = -1; - window.end.days = 0; window.end.timeUnit = "hour"; window.end.timeUnitValue = 120; window.end.coeff = 1; @@ -168,12 +166,10 @@ public void compareToMinute() { Window window = new Window(); window.start = new Window.Endpoint(); window.end = new Window.Endpoint(); - window.start.days = 0; window.start.timeUnit = "minute"; window.start.timeUnitValue = 30; window.start.coeff = -1; - window.end.days = 0; window.end.timeUnit = "minute"; window.end.timeUnitValue = 75; window.end.coeff = 1; @@ -190,12 +186,10 @@ public void compareToSecond() { Window window = new Window(); window.start = new Window.Endpoint(); window.end = new Window.Endpoint(); - window.start.days = 0; window.start.timeUnit = "second"; window.start.timeUnitValue = 30; window.start.coeff = -1; - window.end.days = 0; window.end.timeUnit = "second"; window.end.timeUnitValue = 75; window.end.coeff = 1; diff --git a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java index 062ef9ae..905c1625 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java @@ -12,47 +12,51 @@ public class CriteriaCheckValueTest { private static final CohortExpression WRONG_PRIMARY_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueIncorrect.json")); private static final CohortExpression CORRECT_PRIMARY_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueCorrect.json")); private static final CohortExpression WRONG_ADDITIONAL_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrect.json")); + private static final CohortExpression WRONG_ADDITIONAL_EXPRESSION_HOUR = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrectHour.json")); private static final CohortExpression CORRECT_ADDITIONAL_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrect.json")); + private static final CohortExpression CORRECT_ADDITIONAL_EXPRESSION_MINUTE = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrectMinute.json")); private static final CohortExpression WRONG_INCLUSION_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueIncorrect.json")); private static final CohortExpression CORRECT_INCLUSION_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueCorrect.json")); private static final CohortExpression WRONG_CENSORING_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueIncorrect.json")); private static final CohortExpression CORRECT_CENSORING_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueCorrect.json")); private static final CohortExpression WRONG_EMPTY_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckIncorrect.json")); private static final CohortExpression CORRECT_EMPTY_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckCorrect.json")); private static final CohortExpression EMPTY_CENSORING_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCensoringCriteriaList.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCensoringCriteriaList.json")); private static final CohortExpression EMPTY_PRIMARY_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyPrimaryCriteriaList.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyPrimaryCriteriaList.json")); private static final CohortExpression EMPTY_INCLUSION_RULES_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyInclusionRules.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyInclusionRules.json")); private static final CohortExpression EMPTY_CORRELATED_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCorrelatedCriteria.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCorrelatedCriteria.json")); private static final CohortExpression NO_EXIT_CRITERIA_CHECK = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheck.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheck.json")); private static final CohortExpression NO_EXIT_CRITERIA_CHECK_EARLIEST_EVENT = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheckEarliestEvent.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheckEarliestEvent.json")); private static final int RANGE_PRIMARY_WARNING_COUNT = 148; private static final int CONCEPT_PRIMARY_WARNING_COUNT = 61; @@ -119,12 +123,22 @@ public void checkAdditionalRangeIncorrect() { List warnings = rangeCheck.check(WRONG_ADDITIONAL_EXPRESSION); assertEquals(RANGE_ADDITIONAL_WARNING_COUNT, warnings.size()); } + @Test + public void checkAdditionalRangeIncorrectHour() { + List warnings = rangeCheck.check(WRONG_ADDITIONAL_EXPRESSION_HOUR); + assertEquals(RANGE_ADDITIONAL_WARNING_COUNT, warnings.size()); + } @Test public void checkAdditionalRangeCorrect() { List warnings = rangeCheck.check(CORRECT_ADDITIONAL_EXPRESSION); assertEquals(Collections.emptyList(), warnings); } + @Test + public void checkAdditionalRangeCorrectMinute() { + List warnings = rangeCheck.check(CORRECT_ADDITIONAL_EXPRESSION_MINUTE); + assertEquals(Collections.emptyList(), warnings); + } @Test public void checkAdditionalConceptIncorrect() { diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java index bdfd11c1..fe42e08f 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java @@ -14,6 +14,8 @@ public class DeathTimeWindowCheckTest { CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckIncorrect.json")); private static final CohortExpression CORRECT_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckCorrect.json")); + private static final CohortExpression CORRECT_EXPRESSION_SECOND = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckCorrectSecond.json")); private static final int WARNING_COUNT = 2; @@ -24,10 +26,14 @@ public void checkIncorrect() { List warnings = check.check(INCORRECT_EXPRESSION); assertEquals(WARNING_COUNT, warnings.size()); } - @Test public void checkCorrect() { List warnings = check.check(CORRECT_EXPRESSION); assertEquals(0, warnings.size()); } + @Test + public void checkCorrectSecond() { + List warnings = check.check(CORRECT_EXPRESSION_SECOND); + assertEquals(0, warnings.size()); + } } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java index 06d8652a..6c437421 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java @@ -7,7 +7,6 @@ import java.lang.reflect.Field; import java.sql.SQLException; import com.github.mjeanroy.dbunit.core.dataset.DataSetFactory; -import org.apache.commons.lang3.StringUtils; import org.dbunit.Assertion; import org.dbunit.DatabaseUnitException; import org.dbunit.database.IDatabaseConnection; @@ -359,6 +358,82 @@ public void testGroups() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedTable, actualTable); } + @Test + public void testGroupsHour() throws Exception { + final String RESULTS_SCHEMA = "groupsTest"; + final String[] testDataSetsPrep = new String[] { + "/datasets/vocabulary.json", + "/cohortgeneration/correlatedCriteria/groupTest_PREP.json" + }; + final IDatabaseConnection dbUnitCon = getConnection(); + + // prepare results schema for the specified options.resultSchema + prepareSchema(RESULTS_SCHEMA, RESULTS_DDL_PATH); + + // load test data into DB. + final IDataSet dsPrep = DataSetFactory.createDataSet(testDataSetsPrep); + DatabaseOperation.CLEAN_INSERT.execute(dbUnitCon, dsPrep); // clean load of the DB. Careful, clean means "delete the old stuff" + + CohortExpressionQueryBuilder.BuildExpressionQueryOptions options; + CohortExpression expression; + String cohortSql; + + // this expression has groups defined as: (A OR B OR (C AND D)) + // Cohort 1: Persons 1,2,3 satisfy these criteria through different branches + expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/cohortgeneration/correlatedCriteria/groupExpressionHour.json")); + options = buildExpressionQueryOptions(1, RESULTS_SCHEMA); + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // Cohort 2: Change outer group to 'at least 2' + // person 1 satisfies A, B and (C AND D) + expression.additionalCriteria.type = "AT_LEAST"; + expression.additionalCriteria.count = 2; + options.cohortId = 2; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // cohort 3: change outer group to 'at most 1' + // person 2 and 3 only have 1: 2 has A and 3 has (C AND D) + expression.additionalCriteria.type = "AT_MOST"; + expression.additionalCriteria.count = 2; + options.cohortId = 3; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + + // cohort 4: outer group 'ALL', change inner group to 'at most 0' + // No one passes, since that arrangement is a contradiction + expression.additionalCriteria.type = "ALL"; + expression.additionalCriteria.groups[0].type = "AT_MOST"; + expression.additionalCriteria.groups[0].count = 0; + options.cohortId = 4; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // cohort 5: remove inner group, an empty group should have no impact + // Person 1 has both A and B + expression.additionalCriteria.groups = new CriteriaGroup[0]; + options.cohortId = 5; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // Validate results + // Load actual records from cohort table + final ITable actualTable = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".cohort", String.format("SELECT * from %s ORDER BY cohort_definition_id, subject_id, cohort_start_date", RESULTS_SCHEMA + ".cohort")); + // Load expected data from an XML dataset + final String[] testDataSetsVerify = new String[] {"/cohortgeneration/correlatedCriteria/groupTest_VERIFY.json"}; + final IDataSet expectedDataSet = DataSetFactory.createDataSet(testDataSetsVerify); + final ITable expectedTable = expectedDataSet.getTable(RESULTS_SCHEMA + ".cohort"); + + // Assert actual database table match expected table + Assertion.assertEquals(expectedTable, actualTable); + } /** diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index 83f14077..37dd6a6a 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -35,10 +35,28 @@ public static Window getPrior365Window() { // ... and 0 days before Window.Endpoint endPoint = new Window.Endpoint(); endPoint.coeff = -1; + endPoint.days = 0; endPoint.timeUnitValue = 0; prior365Window.end = endPoint; return prior365Window; } + + public static Window getPriorHoursWindow() { + Window prior365Window = new Window(); + // index starts between 365d before + Window.Endpoint startPoint = new Window.Endpoint(); + startPoint.coeff = -1; + startPoint.days = null; + startPoint.timeUnitValue = 8760; + startPoint.timeUnit = "hour"; + prior365Window.start = startPoint; + // ... and 0 days before + Window.Endpoint endPoint = new Window.Endpoint(); + endPoint.coeff = -1; + endPoint.timeUnitValue = 0; + prior365Window.end = endPoint; + return prior365Window; + } public static Window getAnyTimeWindow() { Window anytimeWindow = new Window(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java index 5988a172..fc863525 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java @@ -176,6 +176,22 @@ public void windowDoseEraTest() throws Exception { } + @Test + public void windowDoseEraTestHour() throws Exception { + + final String resultsSchema = "window_dose_era"; + final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", + "/windowcriteria/windowDoseEra_PREP.json"}; + final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowDoseEra_VERIFY.json"}; + WindowedCriteria wc = new WindowedCriteria(); + wc.criteria = new DoseEra(); // find any dose era + wc.startWindow = CriteriaUtils.getPriorHoursWindow(); + List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, + CriteriaColumn.VALUE_AS_NUMBER, CriteriaColumn.UNIT, CriteriaColumn.DURATION); + this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); + + } + @Test public void windowDrugEraTest() throws Exception { diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index c40cd736..a8b77601 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -455,6 +455,36 @@ public void customEraExitTest() { )); } + @Test + public void customEraExitTestHour() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitHour.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 336 hour between exposures, adding 24 hours after exposure ends, and forcing drug exposure hour supply to: 168 hours." + )); + + } + @Test + public void customEraExitTestMinute() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitMinute.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 20,160 minute between exposures, adding 1,440 minutes after exposure ends, and forcing drug exposure minute supply to: 10,080 minutes." + )); + + } + @Test + public void customEraExitTestSecond() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitSecond.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 1,209,600 second between exposures, adding 86,400 seconds after exposure ends, and forcing drug exposure second supply to: 604,800 seconds." + )); + + } @Test public void conceptSetSimpleTest() { @@ -625,20 +655,20 @@ public void nullCodesetIdTest() { public void nullCohortTest() { exceptionRule.expect(RuntimeException.class); pf.renderCohort((CohortExpression)null); - + } @Test public void nullConceptSetTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSet((ConceptSet)null); - + } @Test public void nullConceptSetListTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSetList((ConceptSet[])null); - + } } diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json new file mode 100644 index 00000000..a1dac8bb --- /dev/null +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json @@ -0,0 +1,562 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Aspirin", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "1191", + "CONCEPT_ID": 1112807, + "CONCEPT_NAME": "Aspirin", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "OccurrenceStartDate": { + "Value": "2020-01-03", + "Op": "lt" + }, + "MeasurementType": [ + { + "CONCEPT_CODE": "10221000000101", + "CONCEPT_ID": 44787754, + "CONCEPT_NAME": "FP70A postgraduate training allowance form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Operator": [ + { + "CONCEPT_CODE": "276139006", + "CONCEPT_ID": 4171756, + "CONCEPT_NAME": "<", + "DOMAIN_ID": "Meas Value Operator", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ValueAsNumber": { + "Value": 3, + "Op": "gt" + }, + "ValueAsConcept": [ + { + "CONCEPT_CODE": "17511009", + "CONCEPT_ID": 4068402, + "CONCEPT_NAME": "Normal absence", + "DOMAIN_ID": "Meas Value", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Unit": [ + { + "CONCEPT_CODE": "%{abnormal}", + "CONCEPT_ID": 9216, + "CONCEPT_NAME": "percent abnormal", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "RangeLow": { + "Value": 2, + "Op": "gt" + }, + "RangeHigh": { + "Value": 2, + "Op": "gt" + }, + "RangeLowRatio": { + "Value": 2, + "Op": "gt" + }, + "RangeHighRatio": { + "Value": 2, + "Op": "gt" + }, + "Age": { + "Value": 2, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "703118005", + "CONCEPT_ID": 45766035, + "CONCEPT_NAME": "Feminine gender", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "101YS0200X", + "CONCEPT_ID": 38003628, + "CONCEPT_NAME": "Behavioral Health & Social Service Providers, Counselor, School", + "DOMAIN_ID": "Provider Specialty", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "OP", + "CONCEPT_ID": 9202, + "CONCEPT_NAME": "Outpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "1440", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DoseEra": { + "EraStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "EraEndDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "DoseValue": { + "Value": 2, + "Op": "gt" + }, + "EraLength": { + "Value": 3, + "Op": "gt" + }, + "AgeAtStart": { + "Value": 4, + "Op": "gt" + }, + "AgeAtEnd": { + "Value": 5, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407378000", + "CONCEPT_ID": 4251434, + "CONCEPT_NAME": "Surgically transgendered transsexual, male-to-female", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "2880", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DeviceExposure": { + "UniqueDeviceId": { + "Text": "13", + "Op": "contains" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 2, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "394744001", + "CONCEPT_ID": 4215271, + "CONCEPT_NAME": "Gender unspecified", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.01", + "CONCEPT_ID": 38003574, + "CONCEPT_NAME": "Asian Indian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-02", + "Op": "lt" + } + } + ], + "Groups": [] + }, + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "ConditionType": [ + { + "CONCEPT_CODE": "10251000000106", + "CONCEPT_ID": 45770878, + "CONCEPT_NAME": "FP75 leave payment form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "StopReason": { + "Text": "stop", + "Op": "contains" + }, + "Age": { + "Value": 1, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "703117000", + "CONCEPT_ID": 45766034, + "CONCEPT_NAME": "Masculine gender", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "LTCP", + "CONCEPT_ID": 42898160, + "CONCEPT_NAME": "Long Term Care Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "4320", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 2, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407379008", + "CONCEPT_ID": 4231242, + "CONCEPT_NAME": "Surgically transgendered transsexual, female-to-male", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.01", + "CONCEPT_ID": 38003574, + "CONCEPT_NAME": "Asian Indian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-01", + "Op": "lt" + } + } + ], + "Groups": [] + }, + "EraStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "EraEndDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceCount": { + "Value": 1, + "Op": "lt" + }, + "EraLength": { + "Value": 1, + "Op": "lt" + }, + "AgeAtStart": { + "Value": 2, + "Op": "lt" + }, + "AgeAtEnd": { + "Value": 2, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407375002", + "CONCEPT_ID": 4234363, + "CONCEPT_NAME": "Surgically transgendered transsexual", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "5460", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 1, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407375002", + "CONCEPT_ID": 4234363, + "CONCEPT_NAME": "Surgically transgendered transsexual", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.02", + "CONCEPT_ID": 38003575, + "CONCEPT_NAME": "Bangladeshi", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Hispanic", + "CONCEPT_ID": 38003563, + "CONCEPT_NAME": "Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2019-12-31", + "Op": "lt" + } + } + ], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 1 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=6.1.0" +} \ No newline at end of file diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json index 266cc3e9..c85cfe33 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json @@ -310,8 +310,7 @@ "CollapseSettings": { "CollapseType": "ERA", "EraPadUnit": "hour", - "EraPadUnitValue": 0, - "EraPad": 0 + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json new file mode 100644 index 00000000..9c69f542 --- /dev/null +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json @@ -0,0 +1,329 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Aspirin", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "1191", + "CONCEPT_ID": 1112807, + "CONCEPT_NAME": "Aspirin", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": -3, + "PostDays": -2 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "OccurrenceStartDate": { + "Op": "lt" + }, + "MeasurementType": [], + "Operator": [], + "ValueAsNumber": { + "Op": "gt" + }, + "ValueAsConcept": [], + "Unit": [], + "RangeLow": { + "Op": "gt" + }, + "RangeHigh": { + "Op": "gt" + }, + "RangeLowRatio": { + "Op": "gt" + }, + "RangeHighRatio": { + "Op": "gt" + }, + "Age": { + "Op": "gt" + }, + "Gender": [], + "ProviderSpecialty": [], + "VisitType": [] + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DeviceExposure": { + "UniqueDeviceId": { + "Op": "contains" + } + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 24, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Op": "gt" + }, + "Gender": [], + "Race": [], + "Ethnicity": [], + "OccurrenceStartDate": { + "Op": "lt" + }, + "OccurrenceEndDate": { + "Op": "lt" + } + } + ], + "Groups": [] + }, + "OccurrenceStartDate": { + "Op": "lt" + }, + "OccurrenceEndDate": { + "Op": "lt" + }, + "ConditionType": [], + "StopReason": { + "Op": "contains" + }, + "Age": { + "Op": "lt" + }, + "Gender": [], + "ProviderSpecialty": [], + "VisitType": [] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 24, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DoseEra": { + "EraStartDate": { + "Op": "lt" + }, + "EraEndDate": { + "Op": "lt" + }, + "Unit": [], + "DoseValue": { + "Op": "gt" + }, + "EraLength": { + "Op": "gt" + }, + "AgeAtStart": { + "Op": "gt" + }, + "AgeAtEnd": { + "Op": "gt" + }, + "Gender": [] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 12, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Op": "lt" + }, + "Gender": [], + "Race": [], + "Ethnicity": [], + "OccurrenceStartDate": { + "Op": "lt" + }, + "OccurrenceEndDate": { + "Op": "lt" + } + } + ], + "Groups": [] + }, + "EraStartDate": { + "Value": "2020-01-16", + "Extent": "2020-01-16", + "Op": "bt" + }, + "EraEndDate": { + "Op": "lt" + }, + "OccurrenceCount": { + "Value": -5, + "Op": "lt" + }, + "EraLength": { + "Op": "lt" + }, + "AgeAtStart": { + "Op": "lt" + }, + "AgeAtEnd": { + "Op": "lt" + }, + "Gender": [] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": -72, + "timeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": -48, + "timeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 5, + "Extent": 1, + "Op": "bt" + }, + "Gender": [], + "Race": [], + "Ethnicity": [], + "OccurrenceStartDate": { + "Value": "2020-01-03", + "Extent": "2020-01-32", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Op": "lt" + } + } + ], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=6.1.0" +} \ No newline at end of file diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json b/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json new file mode 100644 index 00000000..906e64e6 --- /dev/null +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json @@ -0,0 +1,140 @@ +{ + "ConceptSets": [ + { + "id": 2, + "name": "ibuprofen", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "5640", + "CONCEPT_ID": 1177480, + "CONCEPT_NAME": "Ibuprofen", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CodesetId": 2 + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "604800", + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 2 + } + } + ], + "ObservationWindow": { + "PriorDays": 14, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": {} + }, + "StartWindow": { + "Start": { + "Days": 7, + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "1209600", + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "QualifiedLimit": { + "Type": "All" + }, + "ExpressionLimit": { + "Type": "Last" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.3.0" +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json index bf25e20e..5a8056ea 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json @@ -50,15 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "OffsetUnit": "day", - "OffsetUnitValue": 90 + "OffsetUnitValue": 90, + "OffsetUnit": "day" } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPadUnitValue": 0, + "EraPadUnit": "day" }, "CensorWindow": { "StartDate": "2000-04-01", diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json new file mode 100644 index 00000000..82d80ba3 --- /dev/null +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json @@ -0,0 +1,169 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Parent Conceptset", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Clinical Finding", + "CONCEPT_CODE": "P1", + "CONCEPT_ID": 1, + "CONCEPT_NAME": "Parent 1", + "DOMAIN_ID": "CONDITION", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "TestVocab" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "ConditionOccurrence": { + "CodesetId": 0 + } + } + ], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ANY", + "CriteriaList": [ + { + "Criteria": { + "DrugExposure": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 720, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "ProcedureOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 720, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DrugExposure": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 1440, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "ProcedureOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 1440, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "All" + }, + "ExpressionLimit": { + "Type": "All" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {} +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 9c3b8584..543fa6d7 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -31,7 +31,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 30, + "EraPad": 43200, "EraPadUnit": "minute", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json index 2fb0db21..b5aebb73 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json @@ -61,7 +61,6 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index bd12cbe7..a064c47e 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -50,10 +50,8 @@ "EndStrategy": { "CustomEra": { "DrugCodesetId": 0, - "GapDays": 60, "GapUnit": "day", "GapUnitValue": 60, - "Offset": 0, "OffsetUnit": "day", "OffsetUnitValue": 0 } @@ -61,7 +59,6 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json b/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json index 549ac6ad..587e1dfe 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json @@ -16,7 +16,7 @@ "observation_period_end_date":"2001-01-01", "period_type_concept_id": 0 } - ], + ], "cdm.drug_exposure": [ { "drug_exposure_id": 1, @@ -66,6 +66,6 @@ "drug_concept_id":2, "drug_exposure_start_date":"2000-10-01", "drug_type_concept_id":0 - } + } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json index 0713b0dc..e66c806f 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json @@ -44,7 +44,6 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/limits/limitExpression.json b/src/test/resources/cohortgeneration/limits/limitExpression.json index b638424b..df0ce0ba 100644 --- a/src/test/resources/cohortgeneration/limits/limitExpression.json +++ b/src/test/resources/cohortgeneration/limits/limitExpression.json @@ -55,8 +55,6 @@ }, "End": { "Days": 1, - "TimeUnitValue": 1, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -80,17 +78,13 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 1, - "OffsetUnit": "day", - "OffsetUnitValue": 1 + "Offset": 1 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 60, - "EraPadUnit": "day", - "EraPadUnitValue": 60 + "EraPad": 60 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json index 4f3fb3b6..ce44ba5f 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json @@ -1,16 +1,40 @@ { "limitTest.cohort": [ { - "cohort_definition_id": 1, - "subject_id": 1, - "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-06-02" + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2000-02-01", + "cohort_end_date":"2000-02-02" }, { - "cohort_definition_id": 1, - "subject_id": 1, - "cohort_start_date": "2000-07-01", - "cohort_end_date": "2000-10-02" + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2000-06-01", + "cohort_end_date":"2000-08-02" + }, + { + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2000-11-01", + "cohort_end_date":"2000-12-02" + }, + { + "cohort_definition_id":2, + "subject_id":1, + "cohort_start_date":"2000-02-01", + "cohort_end_date":"2000-02-02" + }, + { + "cohort_definition_id":3, + "subject_id":1, + "cohort_start_date":"2000-02-01", + "cohort_end_date":"2000-02-02" + }, + { + "cohort_definition_id":5, + "subject_id":1, + "cohort_start_date":"2000-12-01", + "cohort_end_date":"2000-12-02" } ] } \ No newline at end of file diff --git a/src/test/resources/printfriendly/countDistinctCriteria.json b/src/test/resources/printfriendly/countDistinctCriteria.json index f5dbe6d8..69a6d669 100644 --- a/src/test/resources/printfriendly/countDistinctCriteria.json +++ b/src/test/resources/printfriendly/countDistinctCriteria.json @@ -31,14 +31,10 @@ "StartWindow": { "Start": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -82,8 +78,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -111,8 +105,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -136,8 +128,6 @@ "StartWindow": { "Start": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -178,9 +168,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json b/src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json new file mode 100644 index 00000000..466358c7 --- /dev/null +++ b/src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json @@ -0,0 +1,181 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Empty Concept Set", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "ConditionOccurrence": { + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "2010-01-01", + "Op": "gte" + } + } + }, + { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": {} + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 2592000, + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 720, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "Age": { + "Value": 18, + "Extent": 64, + "Op": "bt" + } + } + }, + { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CodesetId": 0, + "Age": { + "Value": 18, + "Op": "gt" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "day", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1, + "CountColumn": "DOMAIN_CONCEPT" + } + }, + { + "Criteria": { + "ConditionOccurrence": { + "CodesetId": 0, + "Age": { + "Value": 18, + "Op": "gt" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "day", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1, + "CountColumn": "START_DATE" + } + }, + { + "Criteria": { + "ConditionOccurrence": { + "Age": { + "Value": 64, + "Op": "lt" + } + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 0, + "TimeUnit": "day", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1, + "CountColumn": "VISIT_ID" + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0 + } + } + ], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "day", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitHour.json b/src/test/resources/printfriendly/customEraExitHour.json new file mode 100644 index 00000000..adc2d8c4 --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitHour.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 336, + "GapUnit": "hour", + "OffsetUnitValue": 24, + "OffsetUnit": "hour", + "DaysSupplyOverride": 168 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitMinute.json b/src/test/resources/printfriendly/customEraExitMinute.json new file mode 100644 index 00000000..55001076 --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitMinute.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 20160, + "GapUnit": "minute", + "OffsetUnitValue": 1440, + "OffsetUnit": "minute", + "DaysSupplyOverride": 10080 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "minute", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitSecond.json b/src/test/resources/printfriendly/customEraExitSecond.json new file mode 100644 index 00000000..6817a66b --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitSecond.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 1209600, + "GapUnit": "second", + "OffsetUnitValue": 86400, + "OffsetUnit": "second", + "DaysSupplyOverride": 604800 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "second", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json index 20ba6752..a3dd827b 100644 --- a/src/test/resources/printfriendly/visitDetail.json +++ b/src/test/resources/printfriendly/visitDetail.json @@ -41,8 +41,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -110,9 +108,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" From eaf44d5a43199fe867de1c47fbf2e9edc6ce7074 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Tue, 9 Jan 2024 18:04:52 +0700 Subject: [PATCH 05/25] fix --- .../circe/check/checkers/Comparisons.java | 40 +- .../checkers/ExitCriteriaDaysOffsetCheck.java | 6 +- .../checkers/FirstTimeInHistoryCheck.java | 7 +- .../circe/check/checkers/RangeCheck.java | 23 +- .../CohortExpressionQueryBuilder.java | 69 ++- .../cohortdefinition/CollapseSettings.java | 11 +- .../cohortdefinition/CustomEraStrategy.java | 12 + .../cohortdefinition/DateOffsetStrategy.java | 5 + .../circe/cohortdefinition/IntervalUnit.java | 17 + .../ohdsi/circe/cohortdefinition/Window.java | 8 + .../resources/SqlRender-1.15.0-SNAPSHOT.jar | Bin 0 -> 57903 bytes .../printfriendly/cohortExpression.ftl | 5 +- .../printfriendly/criteriaTypes.ftl | 6 +- .../printfriendly/endStrategyTypes.ftl | 6 + .../printfriendly/inputTypes.ftl | 24 +- .../sql/customEraStrategy.sql | 6 +- .../sql/dateOffsetStrategy.sql | 2 +- .../cohortdefinition/sql/generateCohort.sql | 4 +- .../circe/check/checkers/ComparisonsTest.java | 52 +- .../checkers/CriteriaCheckValueTest.java | 10 +- .../checkers/DeathTimeWindowCheckTest.java | 7 + .../builders/CriteriaUtils.java | 1 + .../printfriendly/PrintFriendlyTest.java | 30 + ...tionalCriteriaCheckValueCorrectMinute.json | 562 ++++++++++++++++++ .../deathTimeWindowCheckCorrectSecond.json | 140 +++++ .../printfriendly/customEraExitHour.json | 60 ++ .../printfriendly/customEraExitMinute.json | 60 ++ .../printfriendly/customEraExitSecond.json | 60 ++ 28 files changed, 1174 insertions(+), 59 deletions(-) create mode 100644 src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java create mode 100644 src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar create mode 100644 src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json create mode 100644 src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json create mode 100644 src/test/resources/printfriendly/customEraExitHour.json create mode 100644 src/test/resources/printfriendly/customEraExitMinute.json create mode 100644 src/test/resources/printfriendly/customEraExitSecond.json diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index bf2404e3..53ce610e 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -20,9 +20,7 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -30,6 +28,11 @@ import org.ohdsi.circe.vocabulary.Concept; public class Comparisons { + private static final Map TIME_UNIT_CONVERSION = new HashMap<>(); + static { + TIME_UNIT_CONVERSION.put(IntervalUnit.HOUR.getName(), 60 * 60); + TIME_UNIT_CONVERSION.put(IntervalUnit.MINUTE.getName(), 60); + } public static Boolean startIsGreaterThanEnd(NumericRange r) { @@ -102,18 +105,27 @@ public static Predicate compare(Concept source) { .build(); } - public static int compareTo(ObservationFilter filter, Window window) { - - int range1 = filter.postDays + filter.priorDays; - int range2Start = 0, range2End = 0; - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days)) { - range2Start = window.start.coeff * window.start.days; - } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days)) { - range2End = window.end.coeff * window.end.days; - } - return range1 - (range2End - range2Start); + public static int compareTo(ObservationFilter filter, Window window) { + int range1, range2Start, range2End; + if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) || IntervalUnit.DAY.getName().equals(window.start.timeUnit))) { + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + } else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); } + return range1 - (range2End - range2Start); + } + private static int getTimeInSeconds(Window.Endpoint endpoint) { + return Optional.ofNullable(endpoint) + .map(ep -> { + int convertRate = TIME_UNIT_CONVERSION.getOrDefault(ep.timeUnit, 1); + return Objects.nonNull(ep.timeUnitValue) ? ep.coeff * ep.timeUnitValue * convertRate : 0; + }).orElse(0); + } + public static boolean compare(Criteria c1, Criteria c2) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index b8e83804..eadd4103 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -28,7 +28,7 @@ public class ExitCriteriaDaysOffsetCheck extends BaseCheck { - private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: Days offset from start date should be greater than 0"; + private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: %ss offset from start date should be greater than 0"; @Override protected WarningSeverity defineSeverity() { @@ -42,7 +42,7 @@ protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) .isA(DateOffsetStrategy.class) .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offset) - .then(() -> reporter.add(DAYS_OFFSET_WARNING))); + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offsetUnitValue || 0 == dateOffsetStrategy.offset)) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 8280bcd7..7e13a313 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -42,9 +42,10 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni String name = CriteriaNameHelper.getCriteriaName(criteria.criteria) + " at " + groupName; Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) - .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.days != null) || (c.startWindow.end != null - && c.startWindow.end.days != null))) + .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) + || (c.startWindow.end != null && c.startWindow.end.days != null)) + || c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) + || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index e3b2a8eb..831b624c 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -51,17 +51,22 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep } } - private void checkWindow(Window window, WarningReporter reporter, String name) { + private void checkWindow(Window window, WarningReporter reporter, String name) { - if (Objects.nonNull(window)) { - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days) && window.start.days < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.days, "start"); - } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days) && window.end.days < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.days, "end"); - } - } + if (Objects.isNull(window)) { + return; + } + checkAndReportIfNegative(window.start, reporter, name, "start"); + checkAndReportIfNegative(window.end, reporter, name, "end"); + } + + private void checkAndReportIfNegative(Window.Endpoint windowDetails, WarningReporter reporter, String name, String type) { + if (Objects.nonNull(windowDetails) && Objects.nonNull(windowDetails.days) && windowDetails.days < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, windowDetails.days, type); + } else if (Objects.nonNull(windowDetails) && Objects.nonNull(windowDetails.timeUnitValue) && windowDetails.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, windowDetails.timeUnitValue, type); } + } private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index bf65f8ab..fd638256 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -350,8 +350,13 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@finalCohortQuery", getFinalCohortQuery(expression.censorWindow)); resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); - - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + if(expression.collapseSettings.eraPadUnit == null || expression.collapseSettings.eraPadUnit.equals("day")){ + resultSql = StringUtils.replace(resultSql, "@eraPadValue", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@eraPadUnit", "day"); + }else { + resultSql = StringUtils.replace(resultSql, "@eraPadValue", Integer.toString(expression.collapseSettings.eraPadValue)); + resultSql = StringUtils.replace(resultSql, "@eraPadUnit", expression.collapseSettings.eraPadUnit); + } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -546,8 +551,10 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.days != null) { + if (startWindow.start.days != null && (startWindow.start.timeUnit == null || IntervalUnit.DAY.getName().equals(startWindow.start.timeUnit))) { startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); + } else if (startWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); } else { startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -556,9 +563,12 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", startEventDateExpression, startExpression)); } - if (startWindow.end.days != null) { + if (startWindow.end.days != null && (startWindow.end.timeUnit == null || IntervalUnit.DAY.getName().equals(startWindow.end.timeUnit))) { endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); - } else { + }else if(startWindow.end.timeUnitValue != null){ + endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.end.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); + } + else { endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -573,9 +583,12 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit String endIndexDateExpression = (endWindow.useIndexEnd != null && endWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; // for backwards compatability, having a null endWindow.useIndexEnd means they SHOULD use the index end date. String endEventDateExpression = (endWindow.useEventEnd == null || endWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (endWindow.start.days != null) { + if (endWindow.start.days != null && (endWindow.start.timeUnit == null || IntervalUnit.DAY.getName().equals(endWindow.start.timeUnit))) { startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); - } else { + }else if(endWindow.start.timeUnitValue != null){ + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); + } + else { startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -583,9 +596,13 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); } - if (endWindow.end.days != null) { + if (endWindow.end.days != null && (endWindow.end.timeUnit == null || IntervalUnit.DAY.getName().equals(endWindow.end.timeUnit))) { endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); - } else { + } + else if(endWindow.end.timeUnitValue != null){ + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.end.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); + } + else { endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -763,7 +780,13 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + if (strat.offsetUnit == null || IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); + } else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); return strategySql; @@ -777,13 +800,33 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { } String drugExposureEndDateExpression = DEFAULT_DRUG_EXPOSURE_END_DATE_EXPRESSION; - if (strat.daysSupplyOverride != null) { + if (strat.daysSupplyOverride != null && IntervalUnit.DAY.getName().equals(strat.gapUnit)) { drugExposureEndDateExpression = String.format("DATEADD(day,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); + }else if(strat.daysSupplyOverride != null && IntervalUnit.HOUR.getName().equals(strat.gapUnit)){ + drugExposureEndDateExpression = String.format("DATEADD(hour,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); + }else if(strat.daysSupplyOverride != null && IntervalUnit.MINUTE.getName().equals(strat.gapUnit)){ + drugExposureEndDateExpression = String.format("DATEADD(minute,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); + }else if(strat.daysSupplyOverride != null && IntervalUnit.SECOND.getName().equals(strat.gapUnit)){ + drugExposureEndDateExpression = String.format("DATEADD(second,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); } String strategySql = StringUtils.replace(CUSTOM_ERA_STRATEGY_TEMPLATE, "@eventTable", eventTable); strategySql = StringUtils.replace(strategySql, "@drugCodesetId", strat.drugCodesetId.toString()); - strategySql = StringUtils.replace(strategySql, "@gapDays", Integer.toString(strat.gapDays)); - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + if ("day".equals(strat.gapUnit) || strat.gapUnit == null) { + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); + strategySql = StringUtils.replace(strategySql, "@gapUnit", "day"); + }else { + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); + strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); + + } + if("day".equals(strat.offsetUnit) || strat.offsetUnit == null){ + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); + }else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + } + strategySql = StringUtils.replace(strategySql, "@drugExposureEndDateExpression", drugExposureEndDateExpression); return strategySql; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 39db0e79..5cb61c5b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -18,8 +18,9 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; - +@JsonIgnoreProperties(ignoreUnknown = true) public class CollapseSettings { @JsonProperty("CollapseType") @@ -27,5 +28,11 @@ public class CollapseSettings { @JsonProperty("EraPad") public int eraPad = 0; - + + @JsonProperty("EraPadUnit") + public String eraPadUnit = IntervalUnit.DAY.getName(); + + @JsonProperty("EraPadValue") + public int eraPadValue = 0; + } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java index f585e73d..dafce095 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java @@ -18,12 +18,14 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author Chris Knoll */ +@JsonIgnoreProperties(ignoreUnknown = true) public class CustomEraStrategy extends EndStrategy { @JsonProperty("DrugCodesetId") @@ -31,9 +33,19 @@ public class CustomEraStrategy extends EndStrategy { @JsonProperty("GapDays") public int gapDays = 0; + @JsonProperty("GapUnit") + public String gapUnit = IntervalUnit.DAY.getName();; + @JsonProperty("GapUnitValue") + public Integer gapUnitValue = null; @JsonProperty("Offset") public int offset = 0; + @JsonProperty("OffsetUnit") + public String offsetUnit = IntervalUnit.DAY.getName();; + @JsonProperty("OffsetUnitValue") + public int offsetUnitValue = 0; + + @JsonProperty("DaysSupplyOverride") public Integer daysSupplyOverride = null; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 0ed6b8a8..b0d9bb5c 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -36,6 +36,11 @@ public enum DateField { @JsonProperty("Offset") public int offset = 0; + + @JsonProperty("OffsetUnit") + public String offsetUnit = IntervalUnit.DAY.getName();; + @JsonProperty("OffsetUnitValue") + public int offsetUnitValue = 0; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java new file mode 100644 index 00000000..8f90d6c0 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -0,0 +1,17 @@ +package org.ohdsi.circe.cohortdefinition; + +public enum IntervalUnit { + DAY("day"), + HOUR("hour"), + MINUTE("minute"), + SECOND("second"); + private final String name; + + IntervalUnit(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index c1a2563b..9ebc1289 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -18,20 +18,28 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author cknoll1 */ +@JsonIgnoreProperties(ignoreUnknown = true) public class Window { + @JsonIgnoreProperties(ignoreUnknown = true) public static class Endpoint { @JsonProperty("Days") public Integer days; @JsonProperty("Coeff") public int coeff; + @JsonProperty("TimeUnitValue") + public Integer timeUnitValue; + + @JsonProperty("TimeUnit") + public String timeUnit = IntervalUnit.DAY.getName(); } @JsonProperty("Start") diff --git a/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar b/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..92c04a2c63ec1ba3d1919cf086c50f2c80141970 GIT binary patch literal 57903 zcmagF1B@q8wC>xs?Vh%6+dXaD#HE>>-N_P~F{Z+~h?pmhkk(^}xOyeNGtS@N%N z1tM*~jHn02<|KX7{5hi}b$_=znMb9rph>2L2DTv~zN1 z`2R-!<4OOC7(2QC4;a`#%-+%be;{D~7h!K<;$-ZdG=$Q8KkNF?IZ3^Z#!t2_x}&&vF3)rKJM{{rdNi z|6WN)(?2$b#-_HWcFqch&d#Qec24yFR`|-t%WH!j@bdG)`+@%_d5UDNRCl7i*&$Q9 zB|}tO(~*qJ5~oZ{_f(^Xu03&+l+R3Y>m_UM*Z1R@vTdJbi6>_CNpBoY zvycjk`V7WIzxNy77FzYE8Jv~KU%D*=fiMi<(KT*Mc3`lya0c!x5(N)k`&?38Aavl$ z&cBia5OOdL;Mq+$R~Y3YA^b%a0i9ATT&F@`p{CgbOX}g|DZF6zdSp3ZDz1I4`8ZPR zs{T8Wm@8+6y?Ag8N(EIb9vcGXZeF9A>0d~xtT+tR&2u)~xwI6GRJ7+KPOk}?vwqvVvCzeKbI>w^e-%!q$k3KI}PBrX@ISX6iE;3g+Dggxm68hBmtzen7RYC0cW3va9Xq?oVUT)Vg;UqST=P?l|kav=;1Ej zCV`Z{Gt@GaMM2G$YE>jiXvP>lJ7HE5O;rl;T*#M3p|LX}g_0-b>B1j>t*LVROuydd z%njKj@SCHcpt6Y?apwds0lT3K&f%#GPSa#+V{*)UBh+7k@vj=9Da*Zn=gL`&yu;W7 zeNM;KL1GFca!}Wnb42Jil(!y^1Rb_v60&Jg=vE8?j(#a6#CU~YbWwiD#^JRTgYIf6-&YqZK ztPkxOIgKGr?G!{Iw{ScOTyS=*<_7 zxV$M(py)uJaHg>3d_?upJssKcRJfcDsOnL>^0azPQENV-Y!V!Lb* z>ea83sT zivJFkRWzRuU47C&ZilF?_gmJabj5ys6~B;6X{DZEZLn$V)J|lD^g=tY`gMg4qHt=T z<8@WKN$R7kS9LmjN7(_U$GMw)G9GWSI3qjlJ5Mpo@(tPWHvQfLf9^L-Cgr}p-v;a( z6S#9{CnZjTxNz9S;upjcf;j?$pe^0vc{8uHSlGCm#LR4qJR^ZKC?X@mi$cjn-;)c| z1u_Db7w32!bZ#DZF+-hcn$`xtRn>-%n3O#rztv-Vys7L{dcF5piM3i16x?E<7msU- zis)>|gB~AoB>1C71!H2s*8ZUCG1&c8lLTT0rZG=0gTY}Ml`ay`EP<@kyW;ouTy$T* z8b?oPy0eznE0jdD=DIj&ac9z?{g@RW&R?Wp_IT-Ekl5+_VHjB(@8}*pWXla7(0`<$ z=pl!P__AHphKvxtT+|M`CF9=|IDekId7fj>?G87C>UD})mI37Y_jpKec&Xm>tov=q zbfnzR?G-u7uCt5toObbDH@6}~S{-&Kw*OwWCgOd4A*&?DZpi5B(T)Gx&DlMQpwJ-!i7qS=%3b?=wu1tjt$of@xI_ZE__E0SAOyujJ# zLRWLDV$uD=9YgXe-@Rmep9^JUXM3aKM#$^^_&T;yeg({FA1+t~8$S?E3#>XeKNH)r z?=;JA`Skt)zpgf$a&OTokiE#$_B4e4cmwe+5g&(JkI%na%U{j^ZuW9l6qY?Jz0Ha+ z7FPOm5B8PpsrG8et`#;GS4h1ZjLY|ISY6F_-^|j{p@w@c^>Q1(e_Onbrb4gDBu4U9 zN`>pulz7BjXEk7CqygWuX2~&Qma>7h1mTeP6WQN*_jGi0a-R%rnW%woIJC05 zA4-sBrgQS$T^ELE_^!()`Plw%ZH4BZV1hZyczGovn4udXO`$Y>l$R>>Hj9orlxJp(it%BmTflmD7 zjO_C6d`fQ~c&m6r%(gY_vG3HAU%qUDe-CKKQEx8?2hn`vuH}{91;ql2(<0ZINW&58 z7BXmS7?Yd9Th*7dfK?$2fnM2P55Hv=R^k=)k!3yVB`Gat`3$X(%F5ASO%X>z;?H)d z;5b+RxL27n0027?`Ri_^1HQK{hyMGz&U)kO{ ziO(@4)9V0yz!v|S3#MXeqF3J`ZjZ5S+XJ>oemhXDb7+DFwb(6ER^RARzExd-n_aRK z0|)PkRjV5dq3t5O*tgM&WXtSOgg3{L1afmU z0g@J0G<>@gQ-Ft%b~-P~Z|FLm-=?qlQg-X>8m`rreWx%x(a_{BOIR2fXr z+b=^;?`aQg*XI7LhxS*De~BGjhCKh>truk3Ix?Du(Y#n^#l0t4zbAA~!U)hYM(PpfOrm~ptz();4D zv1_}Om|Mx>dXJ@l5&bsYe~BTU?H8ebW~8XxX(I$k?xkQpf3Mj!f!TGcqO^`Sr&Hh; z0aN)O%o4nZ<2m2CSzE&{YCj!s$}GN{ZV>VacV9f$-s<6l&)ja9`^B2IEI*Ooil3##yxi zM|nlOB|>B*L{%Q_*>A{|BDJ}mAjx69?3fWb_8=*oO=O4AG)j}WP_PqJe;@@}IWP=X z^D#&+D?@n#0lN@tT^X|FmH~X8vQ>w2*PGR7J*n8U(W8PITU=W?P*Y}C2Tom`BOhzwP^UJmqqQ+Um}m7Y#~2WJ8X)dR#n$7@LS@;)Pc1p zdv`2ypep9Y&|0}w_UEDR&&PZC;5Xw9Xnm{1Y98MD7O8vGsg_3I(l#sq`8d6 zzxKoQ3MOhp%mq=i9>o`E9_Hh(qw|yx7|8g165nwqiT8rQ* z(;w=()r$J(bk;{b^C}=L<#7QeHazt;z`ybB5(SRGc#p|@PTU2v=LHU&h6=>^Be=5= z`{$GW$5fV{z>TUPe03ryNsi**SArBs*=R0^kt~rQoGKv_+?d)b3e3cl?iSqC%)%AY z?7ZgmdXT~;>bppUvSb`8DlZ;P*qD+uA)XWF>+LYIq#h=0L|J2K(1@B;k?$A!%)`68 zD&{l`a>XQ&;mnA5G7th)gp{_w98xa7SmL=c05V@1?)jHWL;yr#BGzK0wDDbGIrh_0 zzG8_rn+R}c_q0PNjS;Zv^W>}irb9gCtYk*Lav)kdRT3MYc(FyJY6GZqqNnl&e2Ru5 z`XV|*<^(56^kcrRJ-P0uGwlV6v|t55ftbOj-oWM7FaMK8w~=e>8{ud5)v@EZy2H0( z3uMwbA*i}w#pW%ZPzefb6rmU_Wwi&N&w9`eoZwEF*~jh&f8417DY|fkl(3>?6q=9B zyE-T*t>7LAFN9P@9b3Boqtha0l>>vQX=qt;zab znj>Qy2m{U0PJ|8=yAVB-koUs1+5<|u#V9H?d(K5ZZQkU?@PVMhUnEb{dy=IJpv$On zWNukjZ3}$bx5+AfL~};)1&NWYkJMCH`A48|Q1gj@Z-&^Ls*y^}4$6f<9aY+P>q()B z0GjavXjBWOAbS#WG&02wF{xBmdR;nE)Y;m4vZ-g@e-uO0++i+EN~HKWe++(gQ?9c&xh**4W6MwQJTgJByzJvp&%HCt4u;+iZ7kjR9IPZeaQpGH zZ-h%mIXZkevfe|jo!o&#*jjf%562ySe-sZPlE4o%JNg1} zqb{nd*1VzX-8&ffV7*_RsHvOv2i?HnuQTGX`N~B&e+#0Mh2w_d#<%r{le*s(H^<@S z&2}BgImY7+Gz6}PR`0qXy857>N*{IptZfVK&pnH?I#WX{1GQ_A%7SsmYA+KN1QPW1 zOA$Uz6r{5Qqu(=Hx}Y|7K71(1Rt3X&W;-Ly&KnbGghi`WaRGG;PH|^*`of6LXRwtQ z`=*1+oT&9{b&In=OR%gpo5^j^Sxpc_iQ1KX3xe^In;AldrgsTDCddioKS`{9{C03G zm;-Y4DhCBSB)DvL>qF7yDItk3>`A2MSvymHf<2NeIB<5*9-6A=YCojovg(qvpc&>C zthTh`oi^ebu7v9S!dNLSeY9WsBN{(VmZ|Cvz82T36(Uf2R;QUr7$u#Xr%g77(xtCt zI!5Nj)gTQZ`+?Jpp(^`qYNK=#(>R1rRP|NVd3H3x?8%NkZu}Lzj($(F&IlFaoUP32 zmm!;$04oiPU|=Ve6_+=A3B;{#7c5iOwV(S;REO!Kn-%Q0-df=@cRS2QyjtI->S@*+S}WJcU!IRFq#5DQcTZGMy+n_s=Y zVVIoa#Ty9ltdm&ZIp-rB(v-OgYf!rZW}3wD6bfw2&5c)C;Pw>3a}_cxFfx&}ON1`A z{Z+o;B&n-q;tm$CU73z`HU=j&|KWZVDOr#^9>4DsC10P9cOLQYqStFNKSlz#K&xaz zPd^12(_w-LU&LG28SF($lIH!GTvP-d1_syj@!KI% zoM$jO#pzv!(NM2~b7^tCq)n+rCj;|B)e?SM1%KbZVg^GjuR|<5ag4JlDGR%DvsKBc ztj;+b?j_80F{^}oX$&<8E0v3Og06u!GIXcC=4XhKnBiS!HXo~25^V5aD@QZB!v<)Y zUIrjiaW7|Lij5*%t~DrKH2C3;l(_3dr$^3V_8z;s1#1o(QYP|_H>j4mbK{cr9OT4A z#6jd+jj?daPw80?TGE2i+qVNeQ$@Z^2pWBNwU@+5!;-i-~GFHVLFVw?+TSEJW+gbtp)Cs`7#pyZG+J&M2Y+a?v&m8F&NOr zXROcio>IMA=YDy8{KqSgPGD9iNoUOEg^h-CWf`1h%E5g{&&-#kHGkMhOrs58JG)!s zbGziUN+bSe+I21*xYU1or|^@pB~$<{DwsvBIrmRK?SpGhBJDAdMl@Wy*g)c{^s92~ z?|L)TYwxga({Plz=YRU<4-Y-vra3pkDONPVJ2T?zc-5C7wFGUoyw>Q3unL?gAYs~CPu1c5vh4&o!(pul2is{Id#Q>|)3^``h z^9%z!-K^4OL4QG`4-CIHb!k==(9)Y#8D#V7@7g(xW3VX=Ayc8CWj8^@(6w8iZ^=gD zhqL=wrN;Hu=D{7EKNa7Gi$rmKY9ZOj)*)=TU0B}1UpcA|N|0s0w7XO@Wm@pcdBV!l z8ZzJd?urvoTCdSPP7jI%;NZO}Ga-cU=#xZO}NP2JPKo^TfwE0VolS zAG5QUKr%l+sf)*!Aoh)|l4CQi^(A2Lc4enMy-;lh|=rx|Cn zOk%B|mG{>Z+0H@c)!BGJG|;f7Hm9)s2rkVJt5K7+u5ftcU7B6bPPh}+1P%4uA!OOj z<`XX;0t5-axhA3N`&hcPC303FoZhNCq>^CPk|rRG-H!pJeNa2M*7)eg_%=sP7veG0 zW{UJQlY2=E>9RZk4kGPPiTaZup6Z*}8+vk4w07YEj#k3)N?Yq?ctKaL!iv1YL(uIevzSu zm5|mf?v>bS@RV+`vL*w&R}td9-wL-II0^cx!Z&JG>Iuz<*@H4qC>soV&PlnI1uscS zbN#^+M@tfke5?pbYIS#o3C{JsZTGtkdzbvFU0YuV&ke`qKDsw}EMe^-lLw@(PLR^H z`#J6>btqH~1}1L2f2CCwc|_Ju;xDLb|K;^_!FZr=m{oXuv7%^Jxq2l*rX^iL%<>T+ z#BT0f^;%=F{TCRAmgdb5-U+chr{qHHyh5+J#6K@7Hk49N`UEzhO}G@q#aI&OK3J{u(|0jHYi^VS!Heq8LZruaX`?7w5~7Z!`MRoO;TR2@EwwZ*T( z4?K6aJ?olj_AAc2JK9vb?uhqtZQg_bRW{wiH`;-@O0WqT>A*;CvzyPP`P+YI2DreM zAokx3#P$??h9$W&ST4m$CS&%TA0Q|2ho2FwD57D1@46-)fSJxg-cDs#F3n!$bh#I( z0dI-*Jx>g-yc;nN%qTHy(@!DL0DFrje@tNVFwpIiZ!pwB(ifThyc+h4v!?6BhjY57 zeHn?WcFYagJK73Ke=@(_waLpX>;0w7*f~3U^&|Z5b<@wW>;Adkl6&IoI>ziooHbSP zfb_hXtM7d$i2Lg8+7J^U=!(lN?2H@TD!*6JQE4gIHG9N=@AXS|Npc?-N>ja&Yn9{J z@e7OI;N;ti?Gdp^ag%z_Rf6h>_13wWGjn14kK(ZkpOv;cnE>5fxs)t9Xu0@IZrV-S z-k~+zX~JRPBVA#<-Mjhn?*l!YXVz0ZJJDel<<-T^wmqgb$6mZa2Jzy@pN?buoXCjZ zq7MRSQ_>-ycsq^Y#f2FC2tfu!8}&DWtyfGBN_7KA&M0?&MKZUj*-gm&9+@cdz*e7g z4wO)&uSz$YPQ`z@_`QwI1~aBhwTq%Iz^lidWgQQIR!<5UMf?phHbjfAlI1x;$=Xke z_`K7Z5nf?Lx?Mg{)r zZ(OxdS3FYmW~Hg+K3PAjK%1d64#=p(uIV>vD9^EMsAP}ppC4Gnkxfm5WrLQe84C^Q4x!OE7W`2djY=KSXq33s&<1p17+q*J&k^`E zoAx}p`iWH2>8Htd9;(}@Hb!%7oY%oHO8bPUu}$1RbE|#emmX=HBYKMP{U$fq5-5x0 zGd&#uU?E;I{-K8WfUj``cWZ=KMVd~sn?EDG9_fozfVnmVh}5k#2N6|0Kwla~VrP4C zh?w0Nd(~o;^N-UPzyD&}w<~UKFrw_iN#&{b4wbRH-go^F^fr^h*W#~2M>KNi{9@#v zNa1IgeFo~xhEH-6c)lKP$s67Vj`^40dNX&GLY(m>j1JEj>MHa>`cmGL+J+^^Q7BsOaa+J>W0&o3-(mv^7b+>s2@?5dgHAM$s%f~;aDz-Hc zzR&Q!(0Bea;wqKQyPDL7y$=WvekxU}bo;cvp~=Oxhji0Thxu{7 zx&psUibmi}_u)y^TsDDEc3#5aLEoff1LyR*zkA23YQkc69Rlm<>WC#=2VNYM1~S76 zY^xe8SF;VHo(rbUtg&28x)S^5lVL6HTxJIr2Xa(sJbM>&JRhzjFzE+$mm~VxUEuny z*6wno8BYucDuV>B9^o-J^3})*jM8mDy4of#C_Cc3E-K)ORX6*6*f+UalvzHGZO-=O zs22I_xc6ZQtJR%bP&%(Rg zx>QSdC>@1sav&T`VIon4e%oAFSnH(P%9vx740ypUp1kbwb}(bbeH6=A?~49tK^B7L z;}zg(sXqRIXpOH2`>yj>WviRWSvT)U8f%fIptj%ck7kq?t6lD5wC^p7@I6RcQFubE zQktY3@UmP;5~LisN*p_$z7{^i@i(hd66vbUa5E zK&9G0Sz)Xjg!r`$MdZXiBs`htUmWiTOk1UsZcTl;bosn)5sP?a5cRc3dUnM5grFhJ zJzV(6#mfuE>$>9VjXTIXJqa+_ zm$j<5?Fx4KH1=kk2kNTq6^Gf%NAKlaDUr8}aC^=WzMuT9So2V+zWNhkPGSUiX@csVkAGeAsG4$S;XYe`nhpNom z;$-E{>gZ(DA&D}<9f@gDBXXh@gy7)15#mB_&d`9>gCTX|IY|+tz7?b2IV0qb2oD~! z@FvW#Jw$aDiRliUAP2JZzYoUVb1Oe0I`T?Sq&#yI51`iw{mbsV(QoP=qhV&MZPdjM z$iCY|ztwh|ii;EF%CZ98xtGcl^2bogI%S~c)x#&k5pQxxIXkuK_xZmE?bi?V5O;0v zg14`!2~iG2U6BcSqmRzZZnillA` zQ*w??n-2tkYV!^iOOVMdrWMSXETkYKNNKjn?|5o7`H&9@+MnfL35Hy7X=GK*AEQ=f zS6+cVU1!Z8y2mRBI+Cm^7CF z{}MY;lbZeX(-D7&!s;)PD(LLfkzFqbV%+fj4u#^(z!XBu6^mOm&5SodzDL}pa{R~a z{t4k9sI-$PdP`fSu*Bbm{^5I5p9b= zQ(dzjxGj>y{e>+NPy1bG+pWxV9wi&M$eLXlKsn==>cvnmwEGG&S9EP&{NVKk{_iPYOl zw;_Dy(A!3#;OEY}(tzl-;|^~d6%My%DeBjOPWC}Mj6P{0Uc=JN%q(C;nf;+QAKVmd zL_L^j8B&U6<94p?s3UdQurB6COJy^r(ogn#1Z!n3oa{`hF+B@C7VYM}QegE_^!{Mj zcVgD9DsB>IYqe+bQ5LSuciFeGRqq~AHU((R=2XeuB)m+K10Tz0)u+=1YF znYtRwL^{!R3%(-n9GuSny&pA6MLtlqaRyO@FehUe2b1W=JP2}3<|PZe%XZZ?H8Bho zF*jIk0G6U?B~)v!AAi5O>Zo0YB(?du>!NMtZ@^lY4Y%!zIP3D<(E6<-e>y$Afy_|V zDdp7IphR4r_t$SO`oWob6BugK>>SWA?UdG*z29A-I%L)(JnM-K#!R|elC5+DSTtuR zx1fORr?8|mgKte#R=J`B)m@%L>RZ_JDAO^2EIIy9*PLmAjkFi8X0WipgSQ*wchc1* z7T!2_xk&2tH`{wv1-tq@kt(Rep(5yVL87G5;FYIisX#3PJzhbKWGpF-0VzWaDJFQ| z1&g`2kr5-pG6xcB(Jbkfzlyi^LmaOHf&lpfXDN3$Uxys}_t(8KEqpml!zDU*-C*XO ztOl&?5}#uWoIMk(ym8KcI2Tqv-||I|f;rGMdQR`z<~nX)#!F2PAR=lWlF!aK%eH~L zM?y8`w`owvIN6^e-Pa@a4R1B9R~&TCf&P`UejJS2>_AWUJYDYPbRBQA)#l%gbS9sv ztUED!AC+1qfo@Fc6f^6zt6-_9=5*1cY67)p4Tzcyc!wcAWe%w|Q`;vyAdqX%)}-82u|NHaOzjZ>>ItvI#j=9|)esXi73 zeWD1vVHZHXn$8~ty-!s2mud&Qi!-#lj3nFXhalPv*|sD4cAWhRYwxkYeOug$KkiA( z)hfDhZNBb&ADRH&4me`^w`9rZA`RgZIHNRNr=f)j;B4*%p?E>T3KlFVGW9^3Z!`Y* z4jPcc(Q1W`=;=W4==q9h6;+z91bz_!8J5Rna^~}DNku?pflJMJrC#3xr6wkb>0h9t zl#~G*(HUan^6D}4UlaJ^WS?2OqqNU#=@WM(K}qjv(yy2<^p>g8-x(;~>}`@ioIo#g z(b+>h^Zxlo&qRLlq~Q`-1t^x}1n9{UsJkCV)SQ3mbqL%|TZ}X#Kk>vv;cUx2FNzt) z4DQfO?=pO69Y+9J2-q5Jk0RVF!IK%Cv`(%}xU_80-tuYf?LIm>VLdHe#Z|c^IISGZ zMMvVN?HNkZ8#?q*Yve}}V9tZ4;%MvR?em3opOtT@<(HDsD#0qbycEYDqgv32a?ee8 z5eWA4910zlmig3dNq(|w8ATfVh5RV+N%XdJVsz}}?SV(KA6O;cKUZH9AI7Ty@TvuN za>Ntxt@o&62A@Mo3uG6Vk&!1kjsoN)VPJ5SEUuzen4azo~wjZQyy>R5(^2J|!|xk2gf6B=Cs{d|;j6K#V`vY;(9 zKHZv+4{2n}MTGvml&q3^1vnL}-slPng^h3-*`rZj>a4vBn;$E#PH_~n&8pE@9(^?I zygyq}fjdjnG4(@aT{?(ym(Fw?j@{Flg(Fwnue`d;m7nXDU$D3a6_Uh(BZx8NQ9j!4 z6W`2YYoICzQYQ7P>CR?qD7E1z@L&p-^J=&~6%rJgV6EXfJ1s8Zs10fHHdX_M#xTGq zsVAb&B|X4#g!fm!;|QfmwNxx+l7&n4goR5n$CGB+r`BldNGZ~jjCZJq8CL;QlBj)I zk)mrXJurZl(Qx>LO7E?HP5=u-EYmzwA4Krs)DaAjgh6v*ftAG>3k;wpqVAULD#hQc7&e~wULbcKkd(v!&xht%Rtp*cT^0%~3rRkLN#RXd1LA9_*7 zG=?2JN1y8WZ0`#7|6X_F?=M%KZ}WC`=BZHrSv=kMbViu2Eqh%p?jJIF)AW_Fl;*`? znl*2~L&J2cxySVq2~T@_=@-*?YF1MzjSLw|6h!I)j9k@@{%U)L$4OuB-cH8Ye=w`= zC$Rr5_~gb?II(ecOP-|O2?)Bk7f-a;1fT61RY@BRo1|?AM`Tt`X$FVBvNQdND~Cg* zBjV9-?(96bec7Y>w_ofF6G^#YsOD1j{L?qWT)FGi+oBNNn1xKC25sn#BGzS5sIkn; z?8ndYrbP>zw_o}D4F!L&Sm+v%o`wQdK>`x)9gZtYk8v4kspN5+;$Nx!@?JgQ`BDjAvj;}A@;bBI zd|&TLW>Fu9phf|xUK}!3pj7hagh!AC_WQ-HPkhLyM&%Yj|Ka9USmi)y6 z)X~s4={^?iS-9{!^I0oQS|3|}Ybo{Ahx5nhb!B_5Txx6d7W#BjmI^ASvAd*CC}2^& z)B`gkfID2T(d%%(s9g_`x!B&)>sa}CaLvFK$_9#>o3p|~cQtCF_wJp7_3(_lPy6CQ zkv}@>3(nl>j7D+Bjoq~Jlq@1p+L;N^#1p| z{mJ1e!M`D=eZXp_S+z@LuGP^m#6Es&U+Kx*{vws@D(dXE=nk;94QZNKQUkY+|Fzu` z^VfdeHLt-w+nR-|q%p;*!6mvbJ&`dgphI;e^<%oLII!y}wnuG)AnoG+n`J_7~j1|?D%UfM~}nY(KQL)*Gez@`fEibIOV zu!{y6kyXa<`^c4X#pm-H6_c1-d}DLLtP>GiGFv|yf2qNVgt^COACa=y zF;^1J{tm5oUwbM7_t?dP`wMv1lou9Vfr-wuzT!l zZ>KOuclrmC4$PA36=$odG;8HRauP(lBr`#k`*La&mDYm&5*NIc)I^i;U2Eoav0=qS zxGca&dGtA%3ep7Avcb70aYj(x%xt2K>UV2C<)D64K(&Mm02G0a!X=aR{I|)+DIP4lHwL;R3NE?p68)2@Y|Fth85S+jyj*#Wa)B z{U&9k0Gz54<-iu#uYpI*7ic7JZw0u@Lh-t}P}GhpsKwB;HJjW(*(9PDFkgaON8C^8 zB?$8gXM`uc{uO9U5`0jlmIX^j%4$e-Bp6uGB$4_b49yA*gP+6Q53rg`ojBj$Y4tqm zp9#m*;ffGO6C=>x%Fw6drbsk+gERgak<60x=0LczGILX3Hp`E!c4gn{b$2eycXZhu z(v?zLGZGFGOj}Qg`ks$j)kVwG#I=3{%lCf$T5~pk-`NqL{c{){k>h*!ovGiz*YOYC z_fB+;;s$fGKsR)8y*U3^TA35X>ffrzVxp%pn*Z+KAsoGV*r;HG+#Afx=XMZVoVKqU zFa`_s`yrYHpdi**4I?EsR>*_y3JD_{01R zSm5UhO=1%DRP~Ed8b^|NgV@$+u9|^}N)-~_*c2jK6%2y(*suDJYRKuIh?7HoR}M1) z_jrRzUyI~!4P7Rq7ZT6Bij-!-8mppFOq&wksO$o@bVYYfYgWif;l?9eDHL2?LNgvikb-+_V0xNuyG{e{Ft zqXMEseOIX{%!dJD{}+%>%1x!aTS+JNrq)_Zq^mlhEU-J}qGmN(x>=nv)+18p(7ps` zh%Dm|_ZC|5LWOV?nAoe-Z=y~oA`sfB= zMl_Hrj9^vT6EJy2UJNP17ONmhE$BGKfQ^OQM5~N4JlsZ2mK17l4t*aSjA5CZ96zn9 z(6sAn%lpQtN#6%SIj7BY&o>O$>q1S_WoQg`h&?3et1>*CHD{0jf|d?-xai+#jn~9M zglOmK;~?a!ram@(j^EUfdDg#%=)laD`4L|mWuEi!ccc7)5;xn*%7HEY=UkE?M5?w) zI^CT{Go_x~b_ZtF`NWEZ$D%>yuK172b{cZ5TK{tDevtuF54Wyk6C5i7Ssb1AKK2y- zzs74=CS`KXx@QgdN0Sc8lX8rYQQ^;N$sW)F+OpYwQW)70Ka+Uy4<0K~T=q1MoX|7) zxhtY;n5pP)C4&MD;X2-sLL61aS=4ejO`Xr^A?V^S*Qy_%`xd1Swl6$WuSdBu3f)IB zUfIlU?ll3Tn!AGYyS*qbBSrACg=ePEWp>|vwp-K(M&M%NrurOZ^f_+suFY_0~1ZDE36!OsGTWB7s zGTJw5_b`|}(Zn6|_UXk5U5q&}%?yd=!$tCfNkg1*4Eu>UpJOI%>-W>L^Y^hF4i3Wo zE$fN-AFIW)C*%jG$xpag)-f`e`%#-|bZyvJ(U-zq#KV3tZC?5z97HlG5t9yBceW;3 znhM$vYVYb;n{&4if)RbAh*QD+mw4U1znQBN)6wVf zC9M-{KYh`wyR*rwyH4s~cTT&j$Y5(49$UteXer)@bn2`W;I;vVS*oy41GaH#)K77U zVuq4B&_uv|bT@f1jYJ(N0?R&H7kP5S2=vz#;;^ytMlQXE&ip1UI<=J@M&XBc@;b>0 z@V-FmNCN`g1w{2G<1q#SEEv}<;+Hk32L1qmjT&jd-C}$$XnBPC2~UUuLwb0>8|(8) zC9$0N_U`a9I!SrDsF?^*5JVQ#7{mx54DJd7Q956|_;xns3 zt|S!RW-jZ9;~J^VK}-ptj`0!EpaDay`^N*es$*HWqdMDWd_7O6K7B{tm58`FTVU&CpJa< zn-LcuIOg=Uu~69#7|fka;S#+^`W-a&uTVS4F+WT(SyaJB3AwDz!Q{KC%myBRMY8t`?k@1YOMxac{R&!zYDv zw&0mcoY0W-TU<_%kPvIiJPQ2PlsyB4ejqFC3XBac;>ihv~s zprf(fD(`^m3U7D>$R*ip8Vk3KBaYcfm(mY(YJ2XRI^_jvXn(%){tSQc(A2FJAcn+a zb^lM?laCbspw_fkbxQbYAX?D@WndXonoQJH@B%VO0{u8&e8j80wdCXQnbO6L^du%# zO=Tu-SF=VY^3)@9Gw%?-CetYq?O55_P5yPjaZz)?@%?-%8AA=c>`My{e+P4ElCnJQ ze$_ZG|3)?tu-*z_pT0S;8j@_%D+?ZN*PBizSDEK8x1g|h8v>G$+Gafvcn=KDIzT}U zohLgToz5L}i=BmLKDdru6m&jOYX;n6kcBU|3Rvrbf52X`%vVrpKH(;WFQ+hSX0pcO zy!j}&3%%Q|U@2Dy03wj}&~Fhz2qGx;+_x)&lk1g`n?I%yj#vW}-F&3Qtn}S}^(B*j z;qvtWI%wey&7-LV@@EM2^wqIC;}T5-W9&FS;545${PEa`t9M|H;I0tBI22gOz`L;V z?#a@vYxB>9M-Aw>T)fm)dA4w_Bb%sU9?YmS1zH*tddO$?1k2?gij(w` zQRCLWAFgIQz08+rh4IZSN)p3cJFP%_TNR^XsL%YH<3uCp>$rE6Hm!IV2HNSa%Vp45 z^Ur$)Uq730!?vLHl`gWm@_6TsI_-fZg%?6IwE3?`7HKa{r6&S%RX(qBW|)9pD8dyI zo(#tq+jHkQcCR}hTMD>y|G-r}Scy?BZLoB{m@5gr4Bl-`Odpy%6$x!jRHixvF$tEs z9}{E}$EP4f?>6F}TqKX1ykZX5nEcp&5R?5%lfD*gnTdD)qop(K_{X0REvqj4X_X!m z*_u4I9!}aUmW1`g41WJixoJMIGx9$>IuVTxNQry{Qz1w18MNHb`=L3`Pe0T_SBDD7 zIHi-$kEiiO{-t*>Q>$#7;ke;7s7yDA*6{xAy7S5XA|u zbgX1Z;qH`_A)MqEKm9k`F*3xWZ=Sr!)r}oYkioh_LcKw-w0&EGVv0Sq=wx~N0pl3X z6y=%fE)*6m)t7Px-KVzh$gAeG8y8eDjOnTsRB#FcvA%t9uj0>sgVOt` zBK%i>S@J_Qt?CJTn*yFcpsgKb1bM9`z8Hi7wks*Dka2-IvV zYS_C=Ra;^5aX9jwx;1)-rf^OnnK#0_pW}x)(7Rdm(3Vs|cZ1sWb}L5W;kMI9(Z6`` zVR3-j&9C=%vtxbmvag{Cj+|L+RJu-{S)9b~V6G2#f!yoQdy3Qo~LDk~(k+k}sG-0PQOT=iJp~i*0kx zm+{Ou5e;sIWQO842=|rANcFgcc(pG@j4IuAMba{8ywR2q$=Mt;_GCkKR0>3aJfWQk zXGSt49WI|jolPR5Vck>t=+b%Bz@69T*l1cI|5aU9y*`R+hFfSa?UNJ9NX%B8ALjtO zd*o0N{6o3(tOLvX(nZ5cuvvJQZ`TzssUS~hRrZO6eS*c{;rOI!A7gar>EjQ zXGf-#T;*gpjtbH4)QQ8p{8Y>lXczR zgCx3qxm?zGw*0RY2u#C=pCm^xSqfzCFr*M=Td!eq@RqKNt*NRN5rMxXvETGf6 z0`H{zdn;JF@w>BrtEN#Ku6NX&jcFLm*I!`V-OP9%at?`Y&FSrUGW+ImGUMy{Yl6ui zy}Ba3-v);ISm{tV^)@oF3PmT$O0V)lX-lC#f0uj^cFX%BSgmdxvV)mp-N56yJ~ONhIu=qw&DsO5J>yUf zJIgF0y;~l%>}t+jUEJr57K{598JY$3o&9Te%-FvIm=u%o+K?)8!4;kmOOIg#01kZY zJf8O2Pkw;32M=CdWxaC%S7DAEpYH zHk@r*l~D#TaZ-<3K9u<4t-w7(Y1u}@aZD6~qIXy=GK6Tna_L~`5kYtiB;&%*uY5LO zupbwSmFmZD4^@i&I#Me11D&Oc{_MwhifEcKwKi$LNwkC|&(Nw$hma4x`$?9*79O~7 z#+XSVtTc*uH#fK5Wo~*FX;_KYwkX^2@6asL)L2awGtsVKEq}-I^2yJf!0}O}PLBp8 z5F~O&NdYW{N5T^zoA`t9{Zs{vE`vUe@{r21EB*CNXn69qYV=CnFFZ;$Dn#f zIF&f#WK38z1XC}GWA>=m*s6;pTY~A*O^nkEnNbv(FiNH360%7$f;x&6QqQ3yV|Bw@ z^hi;D$+7y&G0PyQ(9TO0P8vYpzchqv%bWWG{f9RHPX+$p+PrQ#2ec9Z0Wk^y{ik#J z|JLR|zw>`9^1t-htqtw1vef?5!{T;4Eo&!?1;vPDAeb5-GD<9?K^hDcP!FMi5kiDL z&}TwEDXUR!yJY0E&TpMpgRVtW(qdWD0)rA*-i+JUROeE)zOJUNZF|+I)mZ^`^OM8F zk^vob|JnT_I?Z?C*5kuf6qnQULqwce&E2&)bkWTk!{PqM+_QNYqsQ#-0}SqwG$!8*FleVX0->wlrmkZSxQxCStdJeH@;(Asg#iAqtfCM>f+{bBE~Po{-|u zMky2DQ6y}PXRB*MX3(Q`%&UtvaV5(;Bu#wmg+vc$P<^~1PWr}J@0(MfY-)+XCS1lT zhBYs~y&K${&QB?n2W*m@;p*sO^V9ymdwYsw^sr3!yD95Ow}V6a0lWW#oa;-I<0mZ3 zKDCpdg5qDxl^xYC!)*Np`-XfA7k(2p{ebK8-nk zM0~M7^dD~X-g-Ab zJJNmz?A_1Id>0OTJ<#sfgd`%1)zYO zpD})pq$Ry4dGdy>x;!hw+3Yg@Z6cXqRJW5Q@ehh7hItG~hM-lRDWemL@3Ywk>IO#T zl~^#*qNM*NQ!2Og?vg3}Dgqn>&4gB#8Qc%kVBw_5h#TEJHL^B?E<@CRxuvTyMZf!{8rqRspa%?a)6(c%0p&ld|7rJ`?$h zC6nX9`v$LsMRX6uzNyEXy((if4{;wI@4HCKuZSkVHsotFS;fmgBqeF2kEmWYLd?n) zb(!czF{Ui3AN(a#B0AA1tRw-G>v{f|5HG}m5T(a%wzu4Lwj_El2gE%SSg%Dr@oV@IEU6HHb-|o{12kYf zG9y(3q(&daDdv#_J zBn4E%*WBBh!@m*sMV`BeVt~_mfc;QzvGTAefPq@dsEZH4ByT&B_kGAu6PfjR4W`5R zqU-w#vou!n{oH9MKjrdy*X*}!;eUl&!Bj^z7h-#Pzn@f%A2;Uyp)+p;>z^Sp;lX~u zEso#lp_h;~(B$tV8_{)lG`-FjqA-;ZCb_(Jtk|H4lF1A^nzcZL-xgm1ai}Auo#(F&NcF z;Vc|B zuBAcr2bMK|TqJeueXrC;l#wV7)V&a$s(-{zvml7%bb#FdT4h}W&cB}0CEg%x;3-y! zr?cUETW(K$aU5&mPOaLynXHkrnr-Q;C0%0B1MZha@TP>N_3 zr!8X{EzG$V2%oZ-+4WH(nUeR z(^}`%5>A+~X3DR1;_;ZX7!Or?+`)X)Z0hhIOjw&mNl~ocRQ8Qsg>FP^GN-SU_l7r2 zjO9%r8I+@@i#Z8kSHQwXvEvP4ikz{W1mjU(Sps(M7@K zdhp&DU)DX_b~A$xD^W$k6~09xH?ERVh5M*~gGID*Mbyn30q}@?e?4Q%aJ)pi)0?v18Uc6!MiLmy5dqP4b5U17;xw0Q$)_lGH-+62n6a8O%~<<%2pGhv%gOnC?q} zcd5FMF<*5y3^M;UQ3XxLtQZUIu|xfoPa?``QVJ>u+1w%gOEI82E4UMMW+*R1?CStA#N=3?mXk_WDOAX_8{(LU1tI$3e#%fd6BC~q zm$B4)xe5{^n!|_yAx`^Gs?|=CzySA>x3;yBG!{8oxUN5mX)-T$0bg|(99M68RJYpp z4TvYur87FMIZA5f(>9%eJqSuxDW8K3J|?f_(l#s{mZM6o&?{h?{`R10lDDcq@p$ku z!wrGSS$CDYQxPm4Jv$nhI^!<8ah=GZG%cGf=de--N8Cfis^4Gn{n!PU=aTi5tl?0t zN!MYK{!ou#-ZS)2GSs85Lm|~Iqsf<19BjM8wmtU*N-xu(X;KbK8#~jtYDN2V9hc-q zol6%YSU;U^o>9~+=%p;AwnQh(-l&v8jiOci)mMJnTTW$Ov1HX*DbOkLKs|vFOO1l` z>BGldRU^bg&XD`3VFf5NQ6LqfE zCEqcSmT`xulk<# zq>@Eb^~iTNh>#Pgm)8!1EYmJ%fvK-(&;fC--}(-*3xhm5`oSv7_T ztb3>mrzwkiRBAmr660?%yn>`$CvsVKqY12RCECzKGR^isKj5wno#TU zP1&yr9Pq>5+LtYH5}8xsA-mauKb?q>4HCaoFcYxsbC~x#<#*oNW+0>8kSG)m&c|5Z zPf@hM#hO&VfMue5q^}X_x%o(ssAd>;4flLJMp$@^7-!bepa!q!s3#BRw5ti#Bb8-N zJxlkp4h1if+f%N`MUD-+ShqR_vA2`jS*z=H*pMo&Wv2aW_`?)NW<51@Ed*~u5ii!>dx3g65cS~2up*BB!7M$6pR4)1y zP{|Mj3$r^$?Nw*-c2nO9`j936bZ0H0^h$Hu7m_US-W;hGdm@gFk3fqaZjgLG^j&iB z`f=i&MRFve+$Hbj6ZaY2NtRk+1(EqT>l1GrL5+oT^N<0KtuCl8PT&UO+F#3Y4sIag zYZ+C{9#Ra+GI90QH}3o~CQ%|&IwyoUFrV0?^DW@F^CbPi)UJrG{166_=*r_b)pNQ# zq)(o-H{SR+-tul>J5-Y2Yj_4qQFLdV_Q`l8U8(^e5E?1j*uvjoOZ99svZp5Qfr!u@ zwkr(38mtMu3pyi|*f=m_66&7FIa1h-;i}k0Xo~}|Ib@F3!t;hlXhz(l<6DMqu%6}l z(bT87OmnZe+DmI)rvUF>VD`aLPC0aPXeqab1r=j6X~5cS2IUMCG3UG%mX8x?9-s-$w$9WZcX12PSxzWby9N>zx}1+H!)Q*L3?IFP!YiHVJ<^ z4aeFBi5jl-@#)Ln~cVF0@LZr*7mvI<0 z9$0pePdlE9V>)LB*GSK!03aN)5{Q}&xKLgD;i7BER_6h%X_)r@p)BU4EOL`8b2ra@ zZHz>n8#Y6DqAs>5i{q7*H2qCe1gv(Ne<*j1@<3fxs3VW`8KE-k(U>kei>WP?awF27 zDA<_XGRtXQ0J9+z)tSJ0J6slBQ#1)L$@E*8=7Y1z97hWR+B*05ydpgU65Cf)cCkrr z*797&gTq8yX?n_j^RaJB4AKMl5q5st1C$lAwRe)YI33@7#H+CG0}%7aaw?v0&EF?| z<4cl>PgDydCD(LM(ofj$JBoC(x$%;+;55l#kgcNWQlGecD@sgl7IvfZZn95X{q^V$ zNOqLoN|mrn>MH-o1@vZIypp(`H&B*hgYb9uC;=bGs1nEUmI10e)J^BNPmwUTDk2t8 z6p|^+G|aBdusNhZ3VW4e;;+t_zKfB;s}eWQ8^D8yo6OBacbbP^Kvii*TFlkOGde56 zhwmrvcU3F0&k=eL&mIriL`tX6iLNnnNq7=mykOdXFhkb1&F)B!pXgdAzDJxKQEPyh zvHnKa90Eum*&gn3B7^E&#jWmY0tWT!@gD2^YV~Np?jN+T(`4+!ByBg2**Z-3yuU2q zOknS>59PMCFDPcLPwKR(i*%qKr;n$SQ(6_EvF4-`iq%Mx+p}N~4aCBf_tp}(EE<3= z5=L=FuAItow~*I6Z;y{9)PoW51wGBf*w#a!uZRVuXImd1rGN3fTk)<1crBC+{LPU9 z?#Q*zI$PRmROyCGLjy!*rxa3=zI85tbQ^U#oO}Bg!Te?qVfL2u?^DuzQlhaqe7d4o z6}7s~mPWl^GHH`z&;3?oM2_^fxgbHWU_Z0fJD8&+4XC{!Eeq zQ|gjA(Rl_vd|o(_??8eO$Z1ApEiAPT%kB)aJ%QJM;atM?I)>2c4ZS|6;smBW{VVL+ z!x1JvU)C1z@{quh2ChKXo<(bz>Gan?mF%ta8}oH87Kb?7EzN`aKt;wY{~Pyry!CF% z36y8BwQ_h@?na~Rt7-S$Jeo3vmv~gs`U1#XeY#Xjg*#_%^DKW;N>{mceodLoTl*WJ zmta<|O?l3&))`B?=HKY+?UVJhoClncd=G5Y&GQq%5Ntrsi_#LJ=%|ImP=%I#PR^qh z*R|H_mNx0x9okk2e09Z2SLHvm`Ht2y~x6Zp`eG*S+N+*^Am=Gv~2q760#_) zQ;K~R_qEn6Z)@yAlcpr^i z>+GiD9ypIW5h+gTI8`LCVQ?yno=H_uII_l1YSf(M{2KU0?E}Iy?@wteIm>DqU@`m* z0RysF9$enQ>;vA--1(Y17I$N`5I)q0um744IFnd zFhxO0|5DBVe(Zfl{^|mokePK)km(kUkm598{3|IL94*S3yFlUYOE#tV)Ny>nHDG>p z@i0=?4Qad(s0JnsVc!4vCI8 zkcPCGTU~qiquMI`5v0wOwSjke={3zU5qE^%b7;UsIFHH~KO z3H($HErL7aBw%m==+p!Lu#1OlowM(&eL75}aHU5|j##;Qh+;o}w$(B*+X7wtY-nv~ zu~JD8B}LGQ8=Hx_J4RU-+rVcvVh?v6P{g21i+=UJsvzfwoi}%0Mq)+&W0=$Fu&HpY-`xfqav2uNP zIdS(!qd1{xPm-PIFU)}pX!$JwneNR^5%djjLWJtXNEDlZ;g>l9eqbnqPo@Zz20ik$ z+9nnRW=cjzD~SvH2#uX22~`ouJqUYEaR!Ix6MjT!a`sa*`&uj68cEz{OR9@0=^qk= zZ^|UCPS~_K!N@y?&{eB+O)MAVg^L z;>|lOW_0JNTb5vlDXHvE7_s=iVfH|R;%rgOfkMA@*T@0IOkp1U^1=fWJt9j+?9!cn z>QYivw0hC0TxWYeGfG$qO;tQ5oSP({3$OWcC@Qvi4yE9;Ml7n{^01*B1}EOL7D-n4 zT)BZ`QztrjZx#wqu6=JoJkJBy815$n9T;S_J7unoDG*8HA{MpFA&ya- zZGRhIw+)Zb>eGB#UE6qV^55dZU4OeQI!+RzvJ7!b&X%2h0sO+~Yqu)nNXb)e&# zg=ATNid5{b5@E;EAay=a&H`VL33N-S730_}Zp!^MED$M@W` zW4vkzhf9w^*DyMZc!-S<*}c$^$IP|?x=m9O9uHW+atK>Sp=9Uj6R^N(+wnM@NIT&T z909Hyxf#EwXj!N6lakw!cn1!I$ECC_d-|F0;uBH5LB-tQDc9z@M_;;Dq;)hjlxbz# zhV0s55_E#U(Ay2MsJ7RehIs%|=Jgvl?2b^nwf}yidkK$!sZ;nD(%Of`pSG8(xVO`$ zs~D&hdp+=%xk-h^>MY`F2qK4gh2Y@Nk2!8UH!(|4f>EhLRwy6NmCdNVbZrS|WfW?{ zs1l4>8F+v#Wu0bp$91W1-$@6Rm(QrUX9RMA>JaoMa!B`4pB9PPnuxXj2yF!g+N=^E z@PBud>B#7c4WH0z**&zDBrV_xv?3cf;!e2&CzX!dUf8oAL@5=p@(;5qs<8-$v;eix zdr4Xqye~Mr^9kqnTS-xlX`}25EKVhvgViXCS9NIaJoS-tBn#9?t*jDjlk%|VK=P0k ziEDCtJ7`jRcW?}hKgM7Rcgm+s!BXx@I-NqdjEuX2)�-i4M0y@TeY92;~G0l`m8) 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 j8xQk;-LISczWG1#jxKrv!o% ### Cohort Eras +<#if collapseSettings.eraPadUnit?has_content || collapseSettings.eraPadUnit == "day"> Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPad} days of each other. - +<#else > +Entry events will be combined into cohort eras if they are within ${collapseSettings.eraPadUnitValue} ${collapseSettings.eraPadUnit}s of each other. + <#-- main template: end --> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index ab1392a3..4e08190a 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -279,9 +279,9 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi <#macro CountCriteria countCriteria level=0 indexLabel="cohort entry">having <#if countCriteria.occurrence.type == 0 && countCriteria.occurrence.count == 0>no<#else>${inputTypes.getCountType(countCriteria)} ${countCriteria.occurrence.count}<#-- --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> -<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> +<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue?? || countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue?? || countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??)> <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 8f021d3d..748eb902 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -28,7 +28,13 @@ The cohort end date will be offset from index event's ${utils.optionName(dateOff <#macro CustomEraStrategy s> + <#if s.offsetUnit == "day"> The cohort end date will be based on a continuous exposure to ${utils.codesetName(s.drugCodesetId!"", "_invalid drug specified_")}: allowing ${s.gapDays} days between exposures, adding <@utils.formatValue s.offset "day"/> after exposure ends, and <#if s.daysSupplyOverride??>forcing drug exposure days supply to: <@utils.formatValue s.daysSupplyOverride "day"/>.<#else>using days supply and exposure end date for exposure duration. + <#else > +The cohort end date will be based on a continuous exposure to ${utils.codesetName(s.drugCodesetId!"", "_invalid drug specified_")}: +allowing ${s.gapUnitValue} ${s.offsetUnit} between exposures, adding <@utils.formatValue s.offsetUnitValue s.offsetUnit/> after exposure ends, and <#if +s.daysSupplyOverride??>forcing drug exposure ${s.offsetUnit} supply to: <@utils.formatValue s.daysSupplyOverride s.offsetUnit/>.<#else>using ${s.offsetUnit} supply and exposure end date for exposure duration. + diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 00928c04..ce5c360b 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -90,13 +90,33 @@ END Note!!!! <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> <#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- +--><#if (w.start.timeUnit == "day" && w.end.timeUnit == "day") || (!w.start.timeUnit?? && !w.end.timeUnit??)><#-- --><#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.days??>in the ${w.start.days} days<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --><#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +--><#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else><#-- +--><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.start.timeUnit} ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit} ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit} ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.start.timeUnit} ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--> + +<#--<#function getTimeValue(days, timeUnitValue)><#if days?? && days?has_content><#return days><#elseif timeUnitValue && timeUnitValue?has_content><#return timeUnitValue>--> + +<#--<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#–--> +<#--–><#assign startTime = getTimeValue((w.start.days)!, (w.start.timeUnitValue)!)><#–--> +<#--–><#assign endTime = getTimeValue((w.end.days)!, (w.end.timeUnitValue)!)><#–--> +<#--–><#if !startTime?? && endTime == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> +<#--–><#elseif (endTime!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if startTime??>in the ${startTime} days<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> +<#--–><#elseif !startTime?? && (endTime!0) gt 1 && w.start.coeff == -1>anytime up to ${endTime} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> +<#--–><#elseif !endTime?? && (startTime!0) gt 0 && w.end.coeff ==1> ${startTime} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> +<#--–><#else>between ${startTime!"all"} ${w.start.timeUnit!"day"}s ${temporalDirection(w.start.coeff)} and ${endTime!"all"} ${w.end.timeUnit!"day"}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}--> + + -<#-- User Defined Period --> <#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index c606ec0f..dd71b1f9 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -26,7 +26,7 @@ INTO #strategy_ends from @eventTable et JOIN ( - select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(day,@offset, ENDS.era_end_date) as era_end_date + select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(@offsetUnit,@offsetUnitValue, ENDS.era_end_date) as era_end_date from ( select de.person_id, de.drug_exposure_start_date, MIN(e.END_DATE) as era_end_date @@ -34,7 +34,7 @@ JOIN JOIN ( --cteEndDates - select PERSON_ID, DATEADD(day,-1 * @gapDays,EVENT_DATE) as END_DATE -- unpad the end date by @gapDays + select PERSON_ID, DATEADD(@gapUnit,-1 * @gapUnitValue,EVENT_DATE) as END_DATE -- unpad the end date by @gapDays FROM ( select PERSON_ID, EVENT_DATE, EVENT_TYPE, @@ -49,7 +49,7 @@ JOIN UNION ALL -- add the end dates with NULL as the row number, padding the end dates by @gapDays to allow a grace period for overlapping ranges. - select PERSON_ID, DATEADD(day,@gapDays,DRUG_EXPOSURE_END_DATE), 1 as EVENT_TYPE, NULL + select PERSON_ID, DATEADD(@gapUnit,@gapUnitValue,DRUG_EXPOSURE_END_DATE), 1 as EVENT_TYPE, NULL FROM #drugTarget D ) RAWDATA ) E diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index 16a7e395..cbe82232 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,6 +1,6 @@ -- date offset strategy select event_id, person_id, - case when DATEADD(day,@offset,@dateField) > op_end_date then op_end_date else DATEADD(day,@offset,@dateField) end as end_date + case when DATEADD(@offsetUnit,@offsetUnitValue,@dateField) > op_end_date then op_end_date else DATEADD(@offsetUnit,@offsetUnitValue,@dateField) end as end_date INTO #strategy_ends from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 436f37bd..4c764472 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -63,7 +63,7 @@ from ( --cteEnds JOIN ( -- cteEndDates SELECT person_id - , DATEADD(day,-1 * @eraconstructorpad, event_date) as end_date + , DATEADD(@eraPadUnit,-1 * @eraPadValue, event_date) as end_date FROM ( SELECT @@ -84,7 +84,7 @@ from ( --cteEnds SELECT person_id - , DATEADD(day,@eraconstructorpad,end_date) as end_date + , DATEADD(@eraPadUnit,@eraPadValue,end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index a9edf413..e158557f 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -131,7 +131,57 @@ public void compareTo() { assertEquals(5, Comparisons.compareTo(filter, window)); } - + @Test + public void compareToHours(){ + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.timeUnit = "hour"; + window.start.timeUnitValue = 72; + window.start.coeff = -1; + + window.end.timeUnit = "hour"; + window.end.timeUnitValue = 120; + window.end.coeff = 1; + assertEquals(604800, Comparisons.compareTo(filter, window)); + } + @Test + public void compareToMinute() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.timeUnit = "minute"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.timeUnit = "minute"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + assertEquals(1289700, Comparisons.compareTo(filter, window)); + } + @Test + public void compareToSecond() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.timeUnit = "second"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.timeUnit = "second"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + assertEquals(1295895, Comparisons.compareTo(filter, window)); + } @Test public void compareCriteria() { ConditionEra conditionEra1 = new ConditionEra(); diff --git a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java index 062ef9ae..d5efaee2 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java @@ -18,9 +18,11 @@ public class CriteriaCheckValueTest { private static final CohortExpression WRONG_ADDITIONAL_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrect.json")); + private static final CohortExpression CORRECT_ADDITIONAL_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrect.json")); - + private static final CohortExpression CORRECT_ADDITIONAL_EXPRESSION_MINUTE = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrectMinute.json")); private static final CohortExpression WRONG_INCLUSION_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueIncorrect.json")); private static final CohortExpression CORRECT_INCLUSION_EXPRESSION = @@ -119,12 +121,16 @@ public void checkAdditionalRangeIncorrect() { List warnings = rangeCheck.check(WRONG_ADDITIONAL_EXPRESSION); assertEquals(RANGE_ADDITIONAL_WARNING_COUNT, warnings.size()); } - @Test public void checkAdditionalRangeCorrect() { List warnings = rangeCheck.check(CORRECT_ADDITIONAL_EXPRESSION); assertEquals(Collections.emptyList(), warnings); } + @Test + public void checkAdditionalRangeCorrectMinute() { + List warnings = rangeCheck.check(CORRECT_ADDITIONAL_EXPRESSION_MINUTE); + assertEquals(Collections.emptyList(), warnings); + } @Test public void checkAdditionalConceptIncorrect() { diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java index bdfd11c1..ab81b1ed 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java @@ -14,6 +14,8 @@ public class DeathTimeWindowCheckTest { CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckIncorrect.json")); private static final CohortExpression CORRECT_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckCorrect.json")); + private static final CohortExpression CORRECT_EXPRESSION_SECOND = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckCorrectSecond.json")); private static final int WARNING_COUNT = 2; @@ -30,4 +32,9 @@ public void checkCorrect() { List warnings = check.check(CORRECT_EXPRESSION); assertEquals(0, warnings.size()); } + @Test + public void checkCorrectSecond() { + List warnings = check.check(CORRECT_EXPRESSION_SECOND); + assertEquals(0, warnings.size()); + } } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index 40481a30..e9187c2a 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -38,6 +38,7 @@ public static Window getPrior365Window() { prior365Window.end = endPoint; return prior365Window; } + public static Window getAnyTimeWindow() { Window anytimeWindow = new Window(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index dd384912..b53de04e 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -454,6 +454,36 @@ public void customEraExitTest() { "allowing 14 days between exposures, adding 1 day after exposure ends, and forcing drug exposure days supply to: 7 days." )); + } + @Test + public void customEraExitTestHour() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitHour.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 336 hour between exposures, adding 24 hours after exposure ends, and forcing drug exposure hour supply to: 168 hours." + )); + + } + @Test + public void customEraExitTestSecond() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitSecond.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 1,209,600 second between exposures, adding 86,400 seconds after exposure ends, and forcing drug exposure second supply to: 604,800 seconds." + )); + + } + @Test + public void customEraExitTestMinute() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitMinute.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 20,160 minute between exposures, adding 1,440 minutes after exposure ends, and forcing drug exposure minute supply to: 10,080 minutes." + )); + } @Test diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json new file mode 100644 index 00000000..a1dac8bb --- /dev/null +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json @@ -0,0 +1,562 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Aspirin", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "1191", + "CONCEPT_ID": 1112807, + "CONCEPT_NAME": "Aspirin", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "OccurrenceStartDate": { + "Value": "2020-01-03", + "Op": "lt" + }, + "MeasurementType": [ + { + "CONCEPT_CODE": "10221000000101", + "CONCEPT_ID": 44787754, + "CONCEPT_NAME": "FP70A postgraduate training allowance form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Operator": [ + { + "CONCEPT_CODE": "276139006", + "CONCEPT_ID": 4171756, + "CONCEPT_NAME": "<", + "DOMAIN_ID": "Meas Value Operator", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ValueAsNumber": { + "Value": 3, + "Op": "gt" + }, + "ValueAsConcept": [ + { + "CONCEPT_CODE": "17511009", + "CONCEPT_ID": 4068402, + "CONCEPT_NAME": "Normal absence", + "DOMAIN_ID": "Meas Value", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Unit": [ + { + "CONCEPT_CODE": "%{abnormal}", + "CONCEPT_ID": 9216, + "CONCEPT_NAME": "percent abnormal", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "RangeLow": { + "Value": 2, + "Op": "gt" + }, + "RangeHigh": { + "Value": 2, + "Op": "gt" + }, + "RangeLowRatio": { + "Value": 2, + "Op": "gt" + }, + "RangeHighRatio": { + "Value": 2, + "Op": "gt" + }, + "Age": { + "Value": 2, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "703118005", + "CONCEPT_ID": 45766035, + "CONCEPT_NAME": "Feminine gender", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "101YS0200X", + "CONCEPT_ID": 38003628, + "CONCEPT_NAME": "Behavioral Health & Social Service Providers, Counselor, School", + "DOMAIN_ID": "Provider Specialty", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "OP", + "CONCEPT_ID": 9202, + "CONCEPT_NAME": "Outpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "1440", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DoseEra": { + "EraStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "EraEndDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "DoseValue": { + "Value": 2, + "Op": "gt" + }, + "EraLength": { + "Value": 3, + "Op": "gt" + }, + "AgeAtStart": { + "Value": 4, + "Op": "gt" + }, + "AgeAtEnd": { + "Value": 5, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407378000", + "CONCEPT_ID": 4251434, + "CONCEPT_NAME": "Surgically transgendered transsexual, male-to-female", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "2880", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DeviceExposure": { + "UniqueDeviceId": { + "Text": "13", + "Op": "contains" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 2, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "394744001", + "CONCEPT_ID": 4215271, + "CONCEPT_NAME": "Gender unspecified", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.01", + "CONCEPT_ID": 38003574, + "CONCEPT_NAME": "Asian Indian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-02", + "Op": "lt" + } + } + ], + "Groups": [] + }, + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "ConditionType": [ + { + "CONCEPT_CODE": "10251000000106", + "CONCEPT_ID": 45770878, + "CONCEPT_NAME": "FP75 leave payment form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "StopReason": { + "Text": "stop", + "Op": "contains" + }, + "Age": { + "Value": 1, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "703117000", + "CONCEPT_ID": 45766034, + "CONCEPT_NAME": "Masculine gender", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "LTCP", + "CONCEPT_ID": 42898160, + "CONCEPT_NAME": "Long Term Care Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "4320", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 2, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407379008", + "CONCEPT_ID": 4231242, + "CONCEPT_NAME": "Surgically transgendered transsexual, female-to-male", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.01", + "CONCEPT_ID": 38003574, + "CONCEPT_NAME": "Asian Indian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-01", + "Op": "lt" + } + } + ], + "Groups": [] + }, + "EraStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "EraEndDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceCount": { + "Value": 1, + "Op": "lt" + }, + "EraLength": { + "Value": 1, + "Op": "lt" + }, + "AgeAtStart": { + "Value": 2, + "Op": "lt" + }, + "AgeAtEnd": { + "Value": 2, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407375002", + "CONCEPT_ID": 4234363, + "CONCEPT_NAME": "Surgically transgendered transsexual", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "5460", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 1, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407375002", + "CONCEPT_ID": 4234363, + "CONCEPT_NAME": "Surgically transgendered transsexual", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.02", + "CONCEPT_ID": 38003575, + "CONCEPT_NAME": "Bangladeshi", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Hispanic", + "CONCEPT_ID": 38003563, + "CONCEPT_NAME": "Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2019-12-31", + "Op": "lt" + } + } + ], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 1 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=6.1.0" +} \ No newline at end of file diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json b/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json new file mode 100644 index 00000000..906e64e6 --- /dev/null +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json @@ -0,0 +1,140 @@ +{ + "ConceptSets": [ + { + "id": 2, + "name": "ibuprofen", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "5640", + "CONCEPT_ID": 1177480, + "CONCEPT_NAME": "Ibuprofen", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CodesetId": 2 + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "604800", + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 2 + } + } + ], + "ObservationWindow": { + "PriorDays": 14, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": {} + }, + "StartWindow": { + "Start": { + "Days": 7, + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "1209600", + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "QualifiedLimit": { + "Type": "All" + }, + "ExpressionLimit": { + "Type": "Last" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.3.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitHour.json b/src/test/resources/printfriendly/customEraExitHour.json new file mode 100644 index 00000000..adc2d8c4 --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitHour.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 336, + "GapUnit": "hour", + "OffsetUnitValue": 24, + "OffsetUnit": "hour", + "DaysSupplyOverride": 168 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitMinute.json b/src/test/resources/printfriendly/customEraExitMinute.json new file mode 100644 index 00000000..55001076 --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitMinute.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 20160, + "GapUnit": "minute", + "OffsetUnitValue": 1440, + "OffsetUnit": "minute", + "DaysSupplyOverride": 10080 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "minute", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitSecond.json b/src/test/resources/printfriendly/customEraExitSecond.json new file mode 100644 index 00000000..6817a66b --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitSecond.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 1209600, + "GapUnit": "second", + "OffsetUnitValue": 86400, + "OffsetUnit": "second", + "DaysSupplyOverride": 604800 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "second", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file From 603c8004612b104e97bb26b5ba35af3c9527a06b Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Tue, 9 Jan 2024 19:18:05 +0700 Subject: [PATCH 06/25] update test --- .../CohortExpressionQueryBuilder.java | 2 +- .../cohortdefinition/CollapseSettings.java | 2 +- .../ConditionOccurrenceSqlBuilder.java | 17 +++--- .../printfriendly/inputTypes.ftl | 14 ----- .../circe/check/checkers/ComparisonsTest.java | 52 +------------------ .../builders/WindowCriteria_5_0_0_Test.java | 17 ------ .../censorWindow/censorWindowExpression.json | 6 +-- .../eraDupes/eraDupesExpression.json | 12 ++--- .../eraDupes/eraDupes_PREP.json | 15 ++---- .../eraDupes/eraDupes_VERIFY.json | 2 +- .../exits/continuousExposureExpression.json | 9 ++-- src/test/resources/ddl/cdm_v5.0.sql | 2 +- src/test/resources/ddl/cdm_v5.2.sql | 2 +- src/test/resources/ddl/cdm_v5.3.sql | 2 +- src/test/resources/printfriendly/doseEra.json | 25 +-------- .../resources/printfriendly/drugExposure.json | 13 ++--- 16 files changed, 35 insertions(+), 157 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index fd638256..02cd847c 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -354,7 +354,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@eraPadValue", Integer.toString(expression.collapseSettings.eraPad)); resultSql = StringUtils.replace(resultSql, "@eraPadUnit", "day"); }else { - resultSql = StringUtils.replace(resultSql, "@eraPadValue", Integer.toString(expression.collapseSettings.eraPadValue)); + resultSql = StringUtils.replace(resultSql, "@eraPadValue", Integer.toString(expression.collapseSettings.eraPadUnitValue)); resultSql = StringUtils.replace(resultSql, "@eraPadUnit", expression.collapseSettings.eraPadUnit); } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 5cb61c5b..c70f740d 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -33,6 +33,6 @@ public class CollapseSettings { public String eraPadUnit = IntervalUnit.DAY.getName(); @JsonProperty("EraPadValue") - public int eraPadValue = 0; + public int eraPadUnitValue = 0; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 1a7b1b61..d032505b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -65,7 +65,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w // first if (criteria.first != null && criteria.first == true) { whereClauses.add("C.ordinal = 1"); - query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_datetime, co.condition_occurrence_id) as ordinal"); + query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_date, co.condition_occurrence_id) as ordinal"); } else { query = StringUtils.replace(query, "@ordinalExpression", ""); } @@ -93,14 +93,13 @@ protected List resolveSelectClauses(T criteria) { selectCols.add("co.condition_status_concept_id"); } // dateAdjustment or default start/end dates - if (criteria.dateAdjustment != null) { - selectCols.add( - BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, - criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))", - criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))")); - } else { - selectCols.add("co.condition_start_datetime as start_date, COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime)) as end_date"); - } + if (criteria.dateAdjustment != null) { + selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, + criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", + criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); + } else { + selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + } return selectCols; } diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index ce5c360b..9283e1b4 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -104,20 +104,6 @@ END Note!!!! --><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit} ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.start.timeUnit} ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- --> -<#--<#function getTimeValue(days, timeUnitValue)><#if days?? && days?has_content><#return days><#elseif timeUnitValue && timeUnitValue?has_content><#return timeUnitValue>--> - -<#--<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#–--> -<#--–><#assign startTime = getTimeValue((w.start.days)!, (w.start.timeUnitValue)!)><#–--> -<#--–><#assign endTime = getTimeValue((w.end.days)!, (w.end.timeUnitValue)!)><#–--> -<#--–><#if !startTime?? && endTime == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> -<#--–><#elseif (endTime!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if startTime??>in the ${startTime} days<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> -<#--–><#elseif !startTime?? && (endTime!0) gt 1 && w.start.coeff == -1>anytime up to ${endTime} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> -<#--–><#elseif !endTime?? && (startTime!0) gt 0 && w.end.coeff ==1> ${startTime} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#–--> -<#--–><#else>between ${startTime!"all"} ${w.start.timeUnit!"day"}s ${temporalDirection(w.start.coeff)} and ${endTime!"all"} ${w.end.timeUnit!"day"}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}--> - - - - <#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 52a7f84f..4ddeb5e8 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -137,57 +137,7 @@ public void compareTo() { assertEquals(5, Comparisons.compareTo(filter, window)); } - @Test - public void compareToHours(){ - ObservationFilter filter = new ObservationFilter(); - filter.postDays = 10; - filter.priorDays = 5; - Window window = new Window(); - window.start = new Window.Endpoint(); - window.end = new Window.Endpoint(); - window.start.timeUnit = "hour"; - window.start.timeUnitValue = 72; - window.start.coeff = -1; - - window.end.timeUnit = "hour"; - window.end.timeUnitValue = 120; - window.end.coeff = 1; - assertEquals(604800, Comparisons.compareTo(filter, window)); - } - @Test - public void compareToMinute() { - ObservationFilter filter = new ObservationFilter(); - filter.postDays = 10; - filter.priorDays = 5; - Window window = new Window(); - window.start = new Window.Endpoint(); - window.end = new Window.Endpoint(); - window.start.timeUnit = "minute"; - window.start.timeUnitValue = 30; - window.start.coeff = -1; - - window.end.timeUnit = "minute"; - window.end.timeUnitValue = 75; - window.end.coeff = 1; - assertEquals(1289700, Comparisons.compareTo(filter, window)); - } - @Test - public void compareToSecond() { - ObservationFilter filter = new ObservationFilter(); - filter.postDays = 10; - filter.priorDays = 5; - Window window = new Window(); - window.start = new Window.Endpoint(); - window.end = new Window.Endpoint(); - window.start.timeUnit = "second"; - window.start.timeUnitValue = 30; - window.start.coeff = -1; - - window.end.timeUnit = "second"; - window.end.timeUnitValue = 75; - window.end.coeff = 1; - assertEquals(1295895, Comparisons.compareTo(filter, window)); - } + @Test public void compareToHours() { ObservationFilter filter = new ObservationFilter(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java index fc863525..5ab6066e 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java @@ -175,23 +175,6 @@ public void windowDoseEraTest() throws Exception { this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); } - - @Test - public void windowDoseEraTestHour() throws Exception { - - final String resultsSchema = "window_dose_era"; - final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", - "/windowcriteria/windowDoseEra_PREP.json"}; - final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowDoseEra_VERIFY.json"}; - WindowedCriteria wc = new WindowedCriteria(); - wc.criteria = new DoseEra(); // find any dose era - wc.startWindow = CriteriaUtils.getPriorHoursWindow(); - List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, - CriteriaColumn.VALUE_AS_NUMBER, CriteriaColumn.UNIT, CriteriaColumn.DURATION); - this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); - - } - @Test public void windowDrugEraTest() throws Exception { diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json index 5a8056ea..d7c071b6 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json @@ -50,15 +50,13 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "OffsetUnitValue": 90, - "OffsetUnit": "day" + "Offset": 90 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnitValue": 0, - "EraPadUnit": "day" + "EraPad": 0 }, "CensorWindow": { "StartDate": "2000-04-01", diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 543fa6d7..0d27bab9 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -2,8 +2,8 @@ "ConceptSets": [], "PrimaryCriteria": { "CriteriaList": [{ - "ConditionOccurrence": { - } + "ConditionOccurrence": { + } }], "ObservationWindow": { "PriorDays": 0, @@ -23,17 +23,13 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 0, - "OffsetUnit": "hour", - "OffsetUnitValue": 0 + "Offset": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 43200, - "EraPadUnit": "minute", - "EraPadUnitValue": 0 + "EraPad": 30 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json index 806f6510..322a0f0c 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json @@ -14,9 +14,8 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-06-16", - "condition_start_datetime":"2009-06-16 00:00:00", "condition_end_date":"2009-06-19", - "condition_end_datetime":"2009-06-19 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -24,9 +23,8 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-07-01", - "condition_start_datetime":"2009-07-01 00:00:00", "condition_end_date":"2009-07-04", - "condition_end_datetime":"2009-07-04 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -34,9 +32,8 @@ "person_id":1, "condition_concept_id":3, "condition_start_date":"2009-07-27", - "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", - "condition_end_datetime":"2009-07-30 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -44,9 +41,8 @@ "person_id":1, "condition_concept_id":4, "condition_start_date":"2009-07-27", - "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", - "condition_end_datetime":"2009-07-30 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -54,9 +50,8 @@ "person_id":1, "condition_concept_id":5, "condition_start_date":"2009-07-27", - "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", - "condition_end_datetime":"2009-07-30 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json index 20615fa7..f62ad136 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json @@ -4,7 +4,7 @@ "cohort_definition_id":1, "subject_id":1, "cohort_start_date":"2009-06-16", - "cohort_end_date":"2009-06-27" + "cohort_end_date":"2009-07-30" } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index a064c47e..5be887e2 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -50,17 +50,14 @@ "EndStrategy": { "CustomEra": { "DrugCodesetId": 0, - "GapUnit": "day", - "GapUnitValue": 60, - "OffsetUnit": "day", - "OffsetUnitValue": 0 + "GapDays": 60, + "Offset": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/ddl/cdm_v5.0.sql b/src/test/resources/ddl/cdm_v5.0.sql index e4771e5f..eb0cb155 100644 --- a/src/test/resources/ddl/cdm_v5.0.sql +++ b/src/test/resources/ddl/cdm_v5.0.sql @@ -286,7 +286,7 @@ CREATE TABLE @schemaName.condition_occurrence person_id INTEGER NOT NULL , condition_concept_id INTEGER NOT NULL , condition_start_date DATE NOT NULL , - condition_start_datetime TIMESTAMP NOT NULL , + condition_start_datetime TIMESTAMP NULL , condition_end_date DATE NULL , condition_end_datetime TIMESTAMP NULL , condition_type_concept_id INTEGER NOT NULL , diff --git a/src/test/resources/ddl/cdm_v5.2.sql b/src/test/resources/ddl/cdm_v5.2.sql index cc1c9686..55773d3c 100644 --- a/src/test/resources/ddl/cdm_v5.2.sql +++ b/src/test/resources/ddl/cdm_v5.2.sql @@ -292,7 +292,7 @@ CREATE TABLE @schemaName.condition_occurrence person_id INTEGER NOT NULL , condition_concept_id INTEGER NOT NULL , condition_start_date DATE NOT NULL , - condition_start_datetime TIMESTAMP NOT NULL , + condition_start_datetime TIMESTAMP NULL , condition_end_date DATE NULL , condition_end_datetime TIMESTAMP NULL , condition_type_concept_id INTEGER NOT NULL , diff --git a/src/test/resources/ddl/cdm_v5.3.sql b/src/test/resources/ddl/cdm_v5.3.sql index 456ad4b8..d4975350 100644 --- a/src/test/resources/ddl/cdm_v5.3.sql +++ b/src/test/resources/ddl/cdm_v5.3.sql @@ -315,7 +315,7 @@ CREATE TABLE @schemaName.condition_occurrence person_id INTEGER NOT NULL , condition_concept_id INTEGER NOT NULL , condition_start_date DATE NOT NULL , - condition_start_datetime TIMESTAMP NOT NULL , + condition_start_datetime TIMESTAMP NULL , condition_end_date DATE NULL , condition_end_datetime TIMESTAMP NULL , condition_type_concept_id INTEGER NOT NULL , diff --git a/src/test/resources/printfriendly/doseEra.json b/src/test/resources/printfriendly/doseEra.json index 7fb29479..ccbc833e 100644 --- a/src/test/resources/printfriendly/doseEra.json +++ b/src/test/resources/printfriendly/doseEra.json @@ -39,14 +39,10 @@ "StartWindow": { "Start": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -65,13 +61,10 @@ "StartWindow": { "Start": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": -1 }, "End": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "UseEventEnd": false @@ -176,14 +169,10 @@ "StartWindow": { "Start": { "Days": 60, - "TimeUnitValue": 60, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -202,14 +191,10 @@ "StartWindow": { "Start": { "Days": 60, - "TimeUnitValue": 60, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 1, - "TimeUnitValue": 1, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -248,8 +233,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -275,8 +258,6 @@ }, "End": { "Days": 1, - "TimeUnitValue": 1, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -294,10 +275,8 @@ ], "CensoringCriteria": [], "CollapseSettings": { - "EraPad": 0, "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/drugExposure.json b/src/test/resources/printfriendly/drugExposure.json index 7caadd91..fb50b1e2 100644 --- a/src/test/resources/printfriendly/drugExposure.json +++ b/src/test/resources/printfriendly/drugExposure.json @@ -40,8 +40,7 @@ "Coeff": -1 }, "End": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "UseEventEnd": false @@ -59,13 +58,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { - "TimeUnitValue": 0, - "TimeUnit": "day", + "Days": 0, "Coeff": -1 }, "UseEventEnd": false @@ -250,9 +247,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" From fe4f7e5c30af622ebdf724f77caedf22b8317eb6 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Tue, 9 Jan 2024 08:30:12 -0600 Subject: [PATCH 07/25] fix indent on editor --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index d6d2a1b6..11a58414 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,4 @@ root = true [*.{java,sql}] charset = utf-8 indent_style = space -indent_size = 4 +indent_size = 2 From e7f90a6871b57f1ae945f74a2eeff8c389565260 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Mon, 15 Jan 2024 14:37:36 -0600 Subject: [PATCH 08/25] remove jar --- .../resources/SqlRender-1.15.0-SNAPSHOT.jar | Bin 57903 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar diff --git a/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar b/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar deleted file mode 100644 index 92c04a2c63ec1ba3d1919cf086c50f2c80141970..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57903 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�-i4M0y@TeY92;~G0l`m8) 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 j8xQk;-LISczWG1#jxKrv!o% Date: Mon, 18 Dec 2023 10:29:31 +0700 Subject: [PATCH 09/25] Support for datetime calculations in cohort definitions --- .../circe/check/checkers/Comparisons.java | 28 +- .../checkers/ExitCriteriaDaysOffsetCheck.java | 6 +- .../checkers/FirstTimeInHistoryCheck.java | 4 +- .../circe/check/checkers/RangeCheck.java | 9 +- .../check/checkers/TimePatternCheck.java | 4 +- .../CohortExpressionQueryBuilder.java | 23 +- .../cohortdefinition/CollapseSettings.java | 15 +- .../cohortdefinition/DateOffsetStrategy.java | 10 +- .../circe/cohortdefinition/IntervalUnit.java | 20 + .../cohortdefinition/ObservationFilter.java | 2 +- .../ohdsi/circe/cohortdefinition/Window.java | 9 +- .../ConditionOccurrenceSqlBuilder.java | 8 +- .../printfriendly/conceptSet.ftl | 22 +- .../printfriendly/criteriaTypes.ftl | 4 +- .../printfriendly/endStrategyTypes.ftl | 2 +- .../printfriendly/inputTypes.ftl | 10 +- .../sql/dateOffsetStrategy.sql | 7 +- .../cohortdefinition/sql/generateCohort.sql | 6 +- .../circe/check/checkers/ComparisonsTest.java | 22 +- .../builders/CriteriaUtils.java | 4 +- .../additionalCriteriaCheckValueCorrect.json | 3 +- ...additionalCriteriaCheckValueIncorrect.json | 9 +- .../censoringEventCheckValueCorrect.json | 3 +- .../censoringEventCheckValueIncorrect.json | 3 +- .../checkers/childGroupExpression.json | 3 +- .../conceptSetCriteriaCheckCorrect.json | 3 +- .../conceptSetCriteriaCheckIncorrect.json | 3 +- .../contradictionsCriteriaCheckCorrect.json | 3 +- .../contradictionsCriteriaCheckIncorrect.json | 3 +- .../checkers/deathTimeWindowCheckCorrect.json | 12 +- .../deathTimeWindowCheckIncorrect.json | 12 +- .../checkers/domainTypeCheckCorrect.json | 3 +- .../checkers/domainTypeCheckIncorrect.json | 3 +- .../checkers/drugDomainCheckCorrect.json | 3 +- .../checkers/drugDomainCheckIncorrect.json | 3 +- .../checkers/drugEraCheckCorrect.json | 9 +- .../checkers/drugEraCheckIncorrect.json | 3 +- .../duplicatesConceptSetCheckCorrect.json | 3 +- .../duplicatesConceptSetCheckIncorrect.json | 3 +- .../duplicatesCriteriaCheckCorrect.json | 3 +- .../duplicatesCriteriaCheckIncorrect.json | 3 +- .../checkers/emptyCensoringCriteriaList.json | 3 +- .../checkers/emptyCorrelatedCriteria.json | 3 +- .../emptyDemographicCheckCorrect.json | 3 +- .../emptyDemographicCheckIncorrect.json | 3 +- .../checkers/emptyInclusionRules.json | 3 +- .../checkers/emptyPrimaryCriteriaList.json | 3 +- .../eventsProgressionCheckCorrect.json | 3 +- .../eventsProgressionCheckIncorrect.json | 3 +- .../inclusionRulesCheckValueCorrect.json | 3 +- .../inclusionRulesCheckValueIncorrect.json | 3 +- .../checkers/noExitCriteriaCheck.json | 3 +- .../noExitCriteriaCheckEarliestEvent.json | 3 +- .../primaryCriteriaCheckValueCorrect.json | 3 +- .../primaryCriteriaCheckValueIncorrect.json | 3 +- .../checkers/timePatternCheckCorrect.json | 12 +- .../checkers/timePatternCheckIncorrect.json | 21 +- .../resources/checkers/unusedConceptSet.json | 3 +- .../checkers/unusedConceptSetCorrect.json | 3 +- .../allCriteria/allCriteriaExpression.json | 93 ++-- .../censorWindow/censorWindowExpression.json | 6 +- .../censorWindow/censorWindow_PREP.json | 3 + .../correlatedCriteria/countsExpression.json | 6 +- .../correlatedCriteria/countsTest_PREP.json | 2 + .../correlatedCriteria/groupExpression.json | 27 +- .../correlatedCriteria/groupTest_PREP.json | 3 + .../correlatedCriteria/visitExpression.json | 9 +- .../eraDupes/eraDupesExpression.json | 6 +- .../eraDupes/eraDupes_PREP.json | 10 + .../eraDupes/eraDupes_VERIFY.json | 12 + .../exits/censorEventExpression.json | 6 +- .../continuousExposureCensorExpression.json | 3 +- .../exits/continuousExposureExpression.json | 3 +- .../exits/fixedOffsetCensorExpression.json | 6 +- .../exits/fixedOffsetExpression.json | 6 +- .../firstOccurrenceTestExpression.json | 3 +- .../firstOccurrenceTest_PREP.json | 3 + .../inclusionRules/simpleInclusionRule.json | 6 +- .../simpleInclusionRule_PREP.json | 3 + .../limits/limitExpression.json | 9 +- .../limits/limitTest_PREP.json | 7 + .../limits/limitTest_VERIFY.json | 48 +- .../mixedConceptsetsExpression.json | 3 +- .../mixedConceptsets_PREP.json | 2 + .../distinctDefault_PREP.json | 103 ++-- .../corelatedcriteria/distinctStart_PREP.json | 103 ++-- .../corelatedcriteria/distinctVisit_PREP.json | 5 + .../simpleInclusion_PREP.json | 5 + .../conditionOccurrenceDateOffset_PREP.json | 6 + src/test/resources/ddl/cdm_v5.0.sql | 515 +++++++++--------- .../firstOccurrenceTest_PREP.json | 2 + .../printfriendly/allAttributes.json | 48 +- .../resources/printfriendly/anyCondition.json | 3 +- .../printfriendly/censorCriteria.json | 9 +- .../printfriendly/conceptSet_simple.json | 3 +- .../resources/printfriendly/conditionEra.json | 21 +- .../printfriendly/conditionOccurrence.json | 12 +- .../continuousObservation_none.json | 3 +- .../continuousObservation_post.json | 3 +- .../continuousObservation_prior.json | 3 +- .../continuousObservation_priorpost.json | 3 +- .../printfriendly/countCriteria.json | 39 +- .../printfriendly/countDistinctCriteria.json | 18 +- .../printfriendly/customEraExit.json | 3 +- .../resources/printfriendly/dateOffset.json | 6 +- src/test/resources/printfriendly/death.json | 6 +- .../printfriendly/deviceExposure.json | 15 +- src/test/resources/printfriendly/doseEra.json | 33 +- src/test/resources/printfriendly/drugEra.json | 9 +- .../resources/printfriendly/drugExposure.json | 12 +- .../resources/printfriendly/measurement.json | 9 +- .../printfriendly/noCensorCriteria.json | 7 +- .../printfriendly/nullCodesetId.json | 3 +- .../resources/printfriendly/observation.json | 6 +- .../printfriendly/observationPeriod_1.json | 6 +- .../printfriendly/procedureOccurrence.json | 6 +- .../resources/printfriendly/specimen.json | 6 +- src/test/resources/printfriendly/visit.json | 6 +- .../resources/printfriendly/visitDetail.json | 6 +- .../windowConditionOccurrence_PREP.json | 5 + 120 files changed, 1032 insertions(+), 690 deletions(-) create mode 100644 src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index bf2404e3..82ffe189 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -103,18 +103,30 @@ public static Predicate compare(Concept source) { } public static int compareTo(ObservationFilter filter, Window window) { + int range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + int range2Start = getTimeInSeconds(window.start); + int range2End = getTimeInSeconds(window.end); + return range1 - (range2End - range2Start); + } - int range1 = filter.postDays + filter.priorDays; - int range2Start = 0, range2End = 0; - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days)) { - range2Start = window.start.coeff * window.start.days; - } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days)) { - range2End = window.end.coeff * window.end.days; + private static int getTimeInSeconds(Window.Endpoint endpoint) { + if (Objects.isNull(endpoint)) { + return 0; } - return range1 - (range2End - range2Start); + int convertRate; + if (endpoint.timeUnit.equals(IntervalUnit.DAY.getName())) { + convertRate = 24 * 60 * 60; + } else if (endpoint.timeUnit.equals(IntervalUnit.HOUR.getName())) { + convertRate = 60 * 60; + } else if (endpoint.timeUnit.equals(IntervalUnit.MINUTE.getName())) { + convertRate = 60; + } else convertRate = 1; + return Objects.nonNull(endpoint.timeUnitValue) + ? endpoint.coeff * endpoint.timeUnitValue * convertRate + : 0; } + public static boolean compare(Criteria c1, Criteria c2) { boolean result = false; diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index b8e83804..1264be55 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -28,7 +28,7 @@ public class ExitCriteriaDaysOffsetCheck extends BaseCheck { - private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: Days offset from start date should be greater than 0"; + private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: %ss offset from start date should be greater than 0"; @Override protected WarningSeverity defineSeverity() { @@ -42,7 +42,7 @@ protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) .isA(DateOffsetStrategy.class) .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offset) - .then(() -> reporter.add(DAYS_OFFSET_WARNING))); + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 8280bcd7..95f2109f 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -43,8 +43,8 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.days != null) || (c.startWindow.end != null - && c.startWindow.end.days != null))) + && c.startWindow.start.timeUnitValue != null) || (c.startWindow.end != null + && c.startWindow.end.timeUnitValue != null))) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index e3b2a8eb..6b927308 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -54,17 +54,16 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep private void checkWindow(Window window, WarningReporter reporter, String name) { if (Objects.nonNull(window)) { - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days) && window.start.days < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.days, "start"); + if (Objects.nonNull(window.start) && Objects.nonNull(window.start.timeUnitValue) && window.start.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.timeUnitValue, "start"); } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days) && window.end.days < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.days, "end"); + if (Objects.nonNull(window.end) && Objects.nonNull(window.end.timeUnitValue) && window.end.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.timeUnitValue, "end"); } } } private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { - if (Objects.nonNull(filter)) { if (filter.priorDays < 0) { reporter.add(NEGATIVE_VALUE_ERROR, name, filter.priorDays, "prior days"); diff --git a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java index a01c75f6..2a3c5407 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java @@ -83,7 +83,7 @@ private String formatTimeWindow(TimeWindowInfo ti) { } private String formatDays(Window.Endpoint endpoint) { - return Objects.nonNull(endpoint.days) ? String.valueOf(endpoint.days) : "all"; + return Objects.nonNull(endpoint.timeUnitValue) ? String.valueOf(endpoint.timeUnitValue) : "all"; } private String formatCoeff(Window.Endpoint endpoint) { @@ -92,7 +92,7 @@ private String formatCoeff(Window.Endpoint endpoint) { private Integer startDays(Window window) { return Objects.nonNull(window) && Objects.nonNull(window.start) ? - (Objects.nonNull(window.start.days) ? window.start.days : 0) * window.start.coeff : 0; + (Objects.nonNull(window.start.timeUnitValue) ? window.start.timeUnitValue : 0) * window.start.coeff : 0; } class TimeWindowInfo { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index bf65f8ab..50071030 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -351,7 +351,9 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); +// resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -546,8 +548,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.days != null) { - startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); + if (startWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); } else { startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -556,8 +558,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", startEventDateExpression, startExpression)); } - if (startWindow.end.days != null) { - endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); + if (startWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); } else { endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -573,8 +575,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit String endIndexDateExpression = (endWindow.useIndexEnd != null && endWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; // for backwards compatability, having a null endWindow.useIndexEnd means they SHOULD use the index end date. String endEventDateExpression = (endWindow.useEventEnd == null || endWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (endWindow.start.days != null) { - startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); + if (endWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); } else { startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -583,8 +585,8 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); } - if (endWindow.end.days != null) { - endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); + if (endWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); } else { endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } @@ -763,7 +765,8 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); return strategySql; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 39db0e79..0850fb71 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -21,11 +21,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class CollapseSettings { - - @JsonProperty("CollapseType") - public CollapseType collapseType = CollapseType.ERA; - - @JsonProperty("EraPad") - public int eraPad = 0; - + + @JsonProperty("CollapseType") + public CollapseType collapseType = CollapseType.ERA; + @JsonProperty("EraPadUnit") + public String eraPadUnit = IntervalUnit.DAY.getName(); + @JsonProperty("EraPadUnitValue") + public int eraPadUnitValue = 0; + } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 0ed6b8a8..8f610267 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -30,13 +30,15 @@ public enum DateField { StartDate, EndDate } - + @JsonProperty("DateField") public DateField dateField = DateField.StartDate; - @JsonProperty("Offset") - public int offset = 0; - + @JsonProperty("OffsetUnitValue") + public int offsetUnitValue = 0; + @JsonProperty("OffsetUnit") + public String offsetUnit = IntervalUnit.DAY.getName(); + @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { return dispatcher.getStrategySql(this, eventTable); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java new file mode 100644 index 00000000..e55b58e7 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -0,0 +1,20 @@ +package org.ohdsi.circe.cohortdefinition; + +import jdk.nashorn.internal.objects.annotations.Getter; + +public enum IntervalUnit { + DAY("day"), + HOUR("hour"), + MINUTE("minute"), + SECOND("second"); + + private final String name; + + IntervalUnit(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java b/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java index 8be10161..53e514bb 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/ObservationFilter.java @@ -27,6 +27,6 @@ public class ObservationFilter { @JsonProperty("PriorDays") public int priorDays; - @JsonProperty("PostDays") + @JsonProperty("PostDays") public int postDays; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index c1a2563b..e746eb50 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -27,12 +27,13 @@ public class Window { public static class Endpoint { - @JsonProperty("Days") - public Integer days; - + @JsonProperty("TimeUnitValue") + public Integer timeUnitValue; + @JsonProperty("TimeUnit") + public String timeUnit = IntervalUnit.DAY.getName(); @JsonProperty("Coeff") public int coeff; - } + } @JsonProperty("Start") public Endpoint start; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 92880b74..35defc8a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -69,7 +69,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w // first if (criteria.first != null && criteria.first == true) { whereClauses.add("C.ordinal = 1"); - query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_date, co.condition_occurrence_id) as ordinal"); + query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_datetime, co.condition_occurrence_id) as ordinal"); } else { query = StringUtils.replace(query, "@ordinalExpression", ""); } @@ -99,10 +99,10 @@ protected List resolveSelectClauses(T criteria) { // dateAdjustment or default start/end dates if (criteria.dateAdjustment != null) { selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, - criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", - criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); + criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))", + criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))")); } else { - selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + selectCols.add("co.condition_start_datetime as start_date, COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime)) as end_date"); } return selectCols; } diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl index 54a65012..05e69847 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/conceptSet.ftl @@ -1,22 +1,22 @@ <#import "./utils.ftl" as utils> <#list conceptSets as conceptSet> -### ${conceptSet.name} -<#list conceptSet.expression.items> -|Concept ID|Concept Name|Code|Vocabulary|Excluded|Descendants|Mapped -|:---|:---------------------------------------|:--|:-----|:--:|:--:|:--:| -<#items as conceptSetItem> -|${conceptSetItem.concept.conceptId?c}|<#-- + ### ${conceptSet.name} + <#list conceptSet.expression.items> + |Concept ID|Concept Name|Code|Vocabulary|Excluded|Descendants|Mapped + |:---|:---------------------------------------|:--|:-----|:--:|:--:|:--:| + <#items as conceptSetItem> + |${conceptSetItem.concept.conceptId?c}|<#-- -->${conceptSetItem.concept.conceptName}|<#-- -->${conceptSetItem.concept.conceptCode}|<#-- -->${conceptSetItem.concept.vocabularyId}|<#-- -->${utils.renderCheckbox(conceptSetItem.isExcluded)}|<#-- -->${utils.renderCheckbox(conceptSetItem.includeDescendants)}|<#-- -->${utils.renderCheckbox(conceptSetItem.includeMapped)}| - - + -<#else> -There are no concept set items in this concept set. - + <#else> + There are no concept set items in this concept set. + + diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index 715d86d5..fcca4858 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -293,8 +293,8 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> <#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 8f021d3d..a71c1bf4 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -24,7 +24,7 @@ The person exits the cohort at the end of continuous observation. <#assign dateOffsetFieldOptions = [{"id": "StartDate", "name": "start date"}, {"id": "EndDate", "name": "end date"}]> <#macro DateOffsetStrategy s> -The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offset "day"/>. +The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. <#macro CustomEraStrategy s> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 2860374a..a18a704c 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -90,11 +90,11 @@ item?counter gt 1><#if item?counter == list?size> or <#else>, ${item <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> <#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- ---><#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.days??>in the ${w.start.days} days<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +--><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} <#-- User Defined Period --> diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index 16a7e395..b79289dd 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,6 +1,9 @@ -- date offset strategy -select event_id, person_id, - case when DATEADD(day,@offset,@dateField) > op_end_date then op_end_date else DATEADD(day,@offset,@dateField) end as end_date +select event_id, + person_id, + case + when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date + else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date INTO #strategy_ends from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 436f37bd..0a873c67 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -2,7 +2,7 @@ SELECT event_id, person_id, start_date, end_date, op_start_date, op_end_date, visit_occurrence_id INTO #qualified_events -FROM +FROM ( select pe.event_id, pe.person_id, pe.start_date, pe.end_date, pe.op_start_date, pe.op_end_date, row_number() over (partition by pe.person_id order by pe.start_date @QualifiedEventSort) as ordinal, cast(pe.visit_occurrence_id as bigint) as visit_occurrence_id FROM (@primaryEventsQuery) pe @@ -63,7 +63,7 @@ from ( --cteEnds JOIN ( -- cteEndDates SELECT person_id - , DATEADD(day,-1 * @eraconstructorpad, event_date) as end_date + , DATEADD(@era_pad_unit, -1 * @era_pad, event_date) as end_date FROM ( SELECT @@ -84,7 +84,7 @@ from ( --cteEnds SELECT person_id - , DATEADD(day,@eraconstructorpad,end_date) as end_date + , DATEADD(@era_pad_unit, @era_pad, end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index a9edf413..2f4fc4c5 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -6,11 +6,7 @@ import org.ohdsi.circe.vocabulary.Concept; import org.ohdsi.circe.vocabulary.ConceptSetExpression; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.time.LocalDate; -import java.util.Objects; import java.util.function.Predicate; import static java.time.temporal.ChronoUnit.DAYS; @@ -117,19 +113,23 @@ public void compareTo() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.days = 10; + window.start.timeUnitValue = 10; + window.start.timeUnit = IntervalUnit.DAY.getName(); window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.days = 5; + window.end.timeUnitValue = 5; + window.end.timeUnit = IntervalUnit.DAY.getName(); window.end.coeff = 1; assertEquals(0, Comparisons.compareTo(filter, window)); - window.start.days = 5; - window.end.days = 5; + window.start.timeUnitValue = 5; + window.start.timeUnit = IntervalUnit.DAY.getName(); + window.end.timeUnitValue = 5; + window.end.timeUnit = IntervalUnit.DAY.getName(); - assertEquals(5, Comparisons.compareTo(filter, window)); + assertEquals(432000, Comparisons.compareTo(filter, window)); } @Test @@ -243,10 +243,10 @@ public void compareCriteria() { public void isBefore() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.days = 1; + window.start.timeUnitValue = 1; window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.days = 1; + window.end.timeUnitValue = 1; window.end.coeff = 1; assertEquals(false, Comparisons.isBefore(window)); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index 40481a30..c20ca187 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -29,12 +29,12 @@ public static Window getPrior365Window() { // index starts between 365d before Window.Endpoint startPoint = new Window.Endpoint(); startPoint.coeff = -1; - startPoint.days = 365; + startPoint.timeUnitValue = 365; prior365Window.start = startPoint; // ... and 0 days before Window.Endpoint endPoint = new Window.Endpoint(); endPoint.coeff = -1; - endPoint.days = 0; + endPoint.timeUnitValue = 0; prior365Window.end = endPoint; return prior365Window; } diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json index 9204b353..13b41373 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json @@ -539,7 +539,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json index 5da3b99f..be6f11f6 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json @@ -258,11 +258,13 @@ }, "StartWindow": { "Start": { - "Days": -3, + "TimeUnitValue": -3, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": -2, + "TimeUnitValue": -2, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -307,7 +309,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueCorrect.json b/src/test/resources/checkers/censoringEventCheckValueCorrect.json index 7df72672..8b517a9e 100644 --- a/src/test/resources/checkers/censoringEventCheckValueCorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueCorrect.json @@ -408,7 +408,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json index 515aca1b..e791905b 100644 --- a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json @@ -288,7 +288,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/childGroupExpression.json b/src/test/resources/checkers/childGroupExpression.json index 01ed8301..6d040339 100644 --- a/src/test/resources/checkers/childGroupExpression.json +++ b/src/test/resources/checkers/childGroupExpression.json @@ -189,7 +189,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json index 34afceb4..9c9c05e3 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json @@ -115,7 +115,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json index e0d400d5..af967543 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json @@ -89,7 +89,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json index 7f224759..e64e1c55 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json @@ -116,7 +116,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json index 3153dd0c..92b71212 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json @@ -116,7 +116,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json index 16f05ebc..f51ad3d1 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json @@ -39,7 +39,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -83,7 +84,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -104,7 +106,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -131,7 +134,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json index ad419909..2b593bac 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json @@ -39,7 +39,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -83,7 +84,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -104,7 +106,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -131,7 +134,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckCorrect.json b/src/test/resources/checkers/domainTypeCheckCorrect.json index dc401077..678b7562 100644 --- a/src/test/resources/checkers/domainTypeCheckCorrect.json +++ b/src/test/resources/checkers/domainTypeCheckCorrect.json @@ -217,7 +217,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckIncorrect.json b/src/test/resources/checkers/domainTypeCheckIncorrect.json index 34afceb4..9c9c05e3 100644 --- a/src/test/resources/checkers/domainTypeCheckIncorrect.json +++ b/src/test/resources/checkers/domainTypeCheckIncorrect.json @@ -115,7 +115,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckCorrect.json b/src/test/resources/checkers/drugDomainCheckCorrect.json index 42e15e84..0a070252 100644 --- a/src/test/resources/checkers/drugDomainCheckCorrect.json +++ b/src/test/resources/checkers/drugDomainCheckCorrect.json @@ -168,7 +168,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckIncorrect.json b/src/test/resources/checkers/drugDomainCheckIncorrect.json index 4b7364a6..64a99002 100644 --- a/src/test/resources/checkers/drugDomainCheckIncorrect.json +++ b/src/test/resources/checkers/drugDomainCheckIncorrect.json @@ -168,7 +168,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugEraCheckCorrect.json b/src/test/resources/checkers/drugEraCheckCorrect.json index 047e2f50..565aad14 100644 --- a/src/test/resources/checkers/drugEraCheckCorrect.json +++ b/src/test/resources/checkers/drugEraCheckCorrect.json @@ -53,11 +53,13 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": true @@ -76,7 +78,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugEraCheckIncorrect.json b/src/test/resources/checkers/drugEraCheckIncorrect.json index 2fdbba89..22ef9d11 100644 --- a/src/test/resources/checkers/drugEraCheckIncorrect.json +++ b/src/test/resources/checkers/drugEraCheckIncorrect.json @@ -71,7 +71,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json index 4ad7d1c3..4b4cdbe3 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json @@ -50,7 +50,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json index da030ecf..1741beb8 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json @@ -73,7 +73,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json index 6c79f167..b2d56fae 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json @@ -185,7 +185,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json index e726c78c..05b5b93e 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json @@ -532,7 +532,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/emptyCensoringCriteriaList.json b/src/test/resources/checkers/emptyCensoringCriteriaList.json index ff98150d..6dec6bdc 100644 --- a/src/test/resources/checkers/emptyCensoringCriteriaList.json +++ b/src/test/resources/checkers/emptyCensoringCriteriaList.json @@ -49,7 +49,8 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyCorrelatedCriteria.json b/src/test/resources/checkers/emptyCorrelatedCriteria.json index 2b70c327..dbe91807 100644 --- a/src/test/resources/checkers/emptyCorrelatedCriteria.json +++ b/src/test/resources/checkers/emptyCorrelatedCriteria.json @@ -148,7 +148,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckCorrect.json b/src/test/resources/checkers/emptyDemographicCheckCorrect.json index 676d9e06..34a612c0 100644 --- a/src/test/resources/checkers/emptyDemographicCheckCorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckCorrect.json @@ -462,7 +462,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json index f22ae1b2..9ac8d92f 100644 --- a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json @@ -458,7 +458,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyInclusionRules.json b/src/test/resources/checkers/emptyInclusionRules.json index d7def9e6..e0af1736 100644 --- a/src/test/resources/checkers/emptyInclusionRules.json +++ b/src/test/resources/checkers/emptyInclusionRules.json @@ -54,7 +54,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyPrimaryCriteriaList.json b/src/test/resources/checkers/emptyPrimaryCriteriaList.json index c4c7425d..acbd8ac3 100644 --- a/src/test/resources/checkers/emptyPrimaryCriteriaList.json +++ b/src/test/resources/checkers/emptyPrimaryCriteriaList.json @@ -39,7 +39,8 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckCorrect.json b/src/test/resources/checkers/eventsProgressionCheckCorrect.json index 8d8e1118..0cf117c6 100644 --- a/src/test/resources/checkers/eventsProgressionCheckCorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckCorrect.json @@ -82,7 +82,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json index 4ed760a3..5d927fb3 100644 --- a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json @@ -82,7 +82,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json index 121726e5..270f534d 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json @@ -478,7 +478,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json index 50a9a34f..afdc0ef0 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json @@ -268,7 +268,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/noExitCriteriaCheck.json b/src/test/resources/checkers/noExitCriteriaCheck.json index 1cc150d6..822578ab 100644 --- a/src/test/resources/checkers/noExitCriteriaCheck.json +++ b/src/test/resources/checkers/noExitCriteriaCheck.json @@ -77,7 +77,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json index 3b63bd3b..913dc418 100644 --- a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json +++ b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json @@ -77,7 +77,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json index 145d690a..79152e6b 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json @@ -1358,7 +1358,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json index 24864bf0..e1a3dd8b 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json @@ -701,7 +701,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/timePatternCheckCorrect.json b/src/test/resources/checkers/timePatternCheckCorrect.json index 75146327..5b80ee70 100644 --- a/src/test/resources/checkers/timePatternCheckCorrect.json +++ b/src/test/resources/checkers/timePatternCheckCorrect.json @@ -59,7 +59,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -80,7 +81,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -101,7 +103,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -123,7 +126,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/timePatternCheckIncorrect.json b/src/test/resources/checkers/timePatternCheckIncorrect.json index 964b92ad..1488cbe4 100644 --- a/src/test/resources/checkers/timePatternCheckIncorrect.json +++ b/src/test/resources/checkers/timePatternCheckIncorrect.json @@ -88,7 +88,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -109,7 +110,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -130,7 +132,8 @@ }, "StartWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -152,7 +155,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -173,7 +177,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -194,7 +199,8 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -216,7 +222,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/unusedConceptSet.json b/src/test/resources/checkers/unusedConceptSet.json index 3cc898be..b37b5ee4 100644 --- a/src/test/resources/checkers/unusedConceptSet.json +++ b/src/test/resources/checkers/unusedConceptSet.json @@ -4433,7 +4433,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/unusedConceptSetCorrect.json b/src/test/resources/checkers/unusedConceptSetCorrect.json index 38143407..78dae501 100644 --- a/src/test/resources/checkers/unusedConceptSetCorrect.json +++ b/src/test/resources/checkers/unusedConceptSetCorrect.json @@ -4826,7 +4826,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json index 63fae6f7..35d19f07 100644 --- a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json +++ b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json @@ -287,11 +287,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -388,11 +390,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -508,11 +512,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -684,11 +690,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -795,11 +803,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1038,11 +1048,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1259,11 +1271,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1463,11 +1477,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1571,11 +1587,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1749,11 +1767,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -1930,11 +1950,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2092,11 +2114,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2125,11 +2149,13 @@ }, "StartWindow": { "Start": { - "Days": 100, + "TimeUnitValue": 100, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 100, + "TimeUnitValue": 100, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2153,11 +2179,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2175,11 +2203,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -2200,7 +2230,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "minute", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json index d7c071b6..bf25e20e 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json @@ -50,13 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 90 + "OffsetUnit": "day", + "OffsetUnitValue": 90 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": { "StartDate": "2000-04-01", diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json b/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json index 5561c5c2..c1f659b3 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindow_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-02-01", + "condition_start_datetime": "2000-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +29,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-07-01", + "condition_start_datetime": "2000-07-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +37,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json index b257b407..352fd00d 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json @@ -54,7 +54,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -78,7 +79,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json b/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json index 6487957d..d4ea63a1 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/countsTest_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-07", + "condition_start_datetime": "2000-06-07 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-09-01", + "condition_start_datetime": "2000-09-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json index 6867ecd9..e2a4ee39 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json @@ -51,11 +51,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -73,11 +75,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -101,11 +105,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -123,11 +129,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -153,7 +161,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json index c119053c..2d3f8620 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupTest_PREP.json @@ -51,6 +51,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -58,6 +59,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -65,6 +67,7 @@ "person_id":3, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json index 481f28d1..b31aa4e6 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json @@ -52,14 +52,16 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -86,7 +88,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 541c70a4..8b983658 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -23,13 +23,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 0 + "OffsetUnit": "hour", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 30 + "EraPadUnit": "minute", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json index 915128f8..806f6510 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json @@ -14,7 +14,9 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-06-16", + "condition_start_datetime":"2009-06-16 00:00:00", "condition_end_date":"2009-06-19", + "condition_end_datetime":"2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -22,7 +24,9 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-07-01", + "condition_start_datetime":"2009-07-01 00:00:00", "condition_end_date":"2009-07-04", + "condition_end_datetime":"2009-07-04 00:00:00", "condition_type_concept_id":0 }, { @@ -30,7 +34,9 @@ "person_id":1, "condition_concept_id":3, "condition_start_date":"2009-07-27", + "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", + "condition_end_datetime":"2009-07-30 00:00:00", "condition_type_concept_id":0 }, { @@ -38,7 +44,9 @@ "person_id":1, "condition_concept_id":4, "condition_start_date":"2009-07-27", + "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", + "condition_end_datetime":"2009-07-30 00:00:00", "condition_type_concept_id":0 }, { @@ -46,7 +54,9 @@ "person_id":1, "condition_concept_id":5, "condition_start_date":"2009-07-27", + "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", + "condition_end_datetime":"2009-07-30 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json index f62ad136..4d640023 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json @@ -4,6 +4,18 @@ "cohort_definition_id":1, "subject_id":1, "cohort_start_date":"2009-06-16", + "cohort_end_date":"2009-06-19" + }, + { + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2009-07-01", + "cohort_end_date":"2009-07-04" + }, + { + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2009-07-27", "cohort_end_date":"2009-07-30" } ] diff --git a/src/test/resources/cohortgeneration/exits/censorEventExpression.json b/src/test/resources/cohortgeneration/exits/censorEventExpression.json index e2344be6..725f718c 100644 --- a/src/test/resources/cohortgeneration/exits/censorEventExpression.json +++ b/src/test/resources/cohortgeneration/exits/censorEventExpression.json @@ -50,7 +50,8 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 31 + "OffsetUnit": "day", + "OffsetUnitValue": 31 } }, "CensoringCriteria": [ @@ -60,7 +61,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json index 2d5f8d08..09efd2fe 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json @@ -57,7 +57,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index 5be887e2..b142ef89 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -57,7 +57,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json index 7c98a4a8..7faca602 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json @@ -50,13 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 99999 + "OffsetUnit": "day", + "OffsetUnitValue": 99999 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json index 1e446203..0f5ab2e2 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json @@ -50,13 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 31 + "OffsetUnit": "day", + "OffsetUnitValue": 31 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json index 89500baf..e66c806f 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json @@ -44,7 +44,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json index ae4d177b..70b64b4c 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTest_PREP.json @@ -44,6 +44,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-07", + "condition_start_datetime": "2000-06-07 00:00:00", "condition_type_concept_id":0 }, { @@ -51,6 +52,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-09-01", + "condition_start_datetime": "2000-09-01 00:00:00", "condition_type_concept_id":0 }, { @@ -58,6 +60,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json index dc86948e..200fe127 100644 --- a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json +++ b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json @@ -86,7 +86,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -105,7 +106,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json index 7a2bf8d8..49a41753 100644 --- a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json +++ b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +29,7 @@ "person_id":1, "condition_concept_id":3, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +37,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/limits/limitExpression.json b/src/test/resources/cohortgeneration/limits/limitExpression.json index df0ce0ba..23ae40b8 100644 --- a/src/test/resources/cohortgeneration/limits/limitExpression.json +++ b/src/test/resources/cohortgeneration/limits/limitExpression.json @@ -54,7 +54,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -78,13 +79,15 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 1 + "OffsetUnit": "day", + "OffsetUnitValue": 1 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 60 + "EraPadUnit": "day", + "EraPadUnitValue": 60 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/limits/limitTest_PREP.json b/src/test/resources/cohortgeneration/limits/limitTest_PREP.json index af1b7504..cc846b9b 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_PREP.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-02-01", + "condition_start_datetime": "2000-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +30,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +38,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-07-01", + "condition_start_datetime": "2000-07-01 00:00:00", "condition_type_concept_id":0 }, { @@ -42,6 +46,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-08-01", + "condition_start_datetime": "2000-08-01 00:00:00", "condition_type_concept_id":0 }, { @@ -49,6 +54,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-11-01", + "condition_start_datetime": "2000-11-01 00:00:00", "condition_type_concept_id":0 }, { @@ -56,6 +62,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-12-01", + "condition_start_datetime": "2000-12-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json index ce44ba5f..eb27b963 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json @@ -1,40 +1,40 @@ { "limitTest.cohort": [ { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2000-02-01", - "cohort_end_date":"2000-02-02" + "cohort_definition_id": 1, + "subject_id": 1, + "cohort_start_date": "2000-02-01", + "cohort_end_date": "2000-02-02" }, { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2000-06-01", - "cohort_end_date":"2000-08-02" + "cohort_definition_id": 1, + "subject_id": 1, + "cohort_start_date": "2000-06-01", + "cohort_end_date": "2000-08-02" }, { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2000-11-01", - "cohort_end_date":"2000-12-02" + "cohort_definition_id": 1, + "subject_id": 1, + "cohort_start_date": "2000-11-01", + "cohort_end_date": "2000-12-02" }, { - "cohort_definition_id":2, - "subject_id":1, - "cohort_start_date":"2000-02-01", - "cohort_end_date":"2000-02-02" + "cohort_definition_id": 2, + "subject_id": 1, + "cohort_start_date": "2000-02-01", + "cohort_end_date": "2000-02-02" }, { - "cohort_definition_id":3, - "subject_id":1, - "cohort_start_date":"2000-02-01", - "cohort_end_date":"2000-02-02" + "cohort_definition_id": 3, + "subject_id": 1, + "cohort_start_date": "2000-02-01", + "cohort_end_date": "2000-02-02" }, { - "cohort_definition_id":5, - "subject_id":1, - "cohort_start_date":"2000-12-01", - "cohort_end_date":"2000-12-02" + "cohort_definition_id": 5, + "subject_id": 1, + "cohort_start_date": "2000-12-01", + "cohort_end_date": "2000-12-02" } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json index 0957bca8..99a748f6 100644 --- a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json +++ b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsetsExpression.json @@ -71,7 +71,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json index 2e1ac335..0890f2ae 100644 --- a/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json +++ b/src/test/resources/cohortgeneration/mixedConceptsets/mixedConceptsets_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-02-01", + "condition_start_datetime":"2000-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -22,6 +23,7 @@ "condition_concept_id":2, "condition_source_concept_id":3, "condition_start_date":"2000-07-01", + "condition_start_datetime":"2000-07-01 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/corelatedcriteria/distinctDefault_PREP.json b/src/test/resources/corelatedcriteria/distinctDefault_PREP.json index cb1ec56a..d86d6753 100644 --- a/src/test/resources/corelatedcriteria/distinctDefault_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctDefault_PREP.json @@ -1,90 +1,95 @@ { "cdm.person": [ { - "person_id":1, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 1, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 }, { - "person_id":2, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 2, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 } ], "cdm.condition_occurrence": [ { "condition_occurrence_id": 1, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2000-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 1 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 1 }, { "condition_occurrence_id": 2, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 3, - "person_id":1, - "condition_concept_id":3, - "condition_start_date":"2005-01-02", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 3, + "condition_start_date": "2005-01-02", + "condition_start_datetime": "2005-01-02 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 4, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 3 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 3 }, { "condition_occurrence_id": 5, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 4 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 4 } ], - "cdm.observation_period" : [ + "cdm.observation_period": [ { "observation_period_id": 1, - "person_id":1, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 1, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 }, { "observation_period_id": 2, - "person_id":2, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 2, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 } ], - "distinct_default.cohort" : [ + "distinct_default.cohort": [ { "cohort_definition_id": 1, - "subject_id":1, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 1, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" }, { "cohort_definition_id": 1, - "subject_id":2, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 2, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" } ] } \ No newline at end of file diff --git a/src/test/resources/corelatedcriteria/distinctStart_PREP.json b/src/test/resources/corelatedcriteria/distinctStart_PREP.json index 450e2cec..f87db1a2 100644 --- a/src/test/resources/corelatedcriteria/distinctStart_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctStart_PREP.json @@ -1,90 +1,95 @@ { "cdm.person": [ { - "person_id":1, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 1, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 }, { - "person_id":2, - "gender_concept_id":0, - "year_of_birth":0, - "race_concept_id":0, - "ethnicity_concept_id":0 + "person_id": 2, + "gender_concept_id": 0, + "year_of_birth": 0, + "race_concept_id": 0, + "ethnicity_concept_id": 0 } ], "cdm.condition_occurrence": [ { "condition_occurrence_id": 1, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2000-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 1 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 1 }, { "condition_occurrence_id": 2, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 3, - "person_id":1, - "condition_concept_id":2, - "condition_start_date":"2005-01-02", - "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "person_id": 1, + "condition_concept_id": 2, + "condition_start_date": "2005-01-02", + "condition_start_datetime": "2005-01-02 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 4, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 3 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 3 }, { "condition_occurrence_id": 5, - "person_id":2, - "condition_concept_id":2, - "condition_start_date":"2005-01-01", - "condition_type_concept_id":0, - "visit_occurrence_id": 4 + "person_id": 2, + "condition_concept_id": 2, + "condition_start_date": "2005-01-01", + "condition_start_datetime": "2005-01-01 00:00:00", + "condition_type_concept_id": 0, + "visit_occurrence_id": 4 } ], - "cdm.observation_period" : [ + "cdm.observation_period": [ { "observation_period_id": 1, - "person_id":1, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 1, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 }, { "observation_period_id": 2, - "person_id":2, - "observation_period_start_date":"2000-01-01", - "observation_period_end_date":"2010-01-01", + "person_id": 2, + "observation_period_start_date": "2000-01-01", + "observation_period_end_date": "2010-01-01", "period_type_concept_id": 0 } ], - "distinct_start.cohort" : [ + "distinct_start.cohort": [ { "cohort_definition_id": 1, - "subject_id":1, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 1, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" }, { "cohort_definition_id": 1, - "subject_id":2, - "cohort_start_date":"2006-01-01", - "cohort_end_date":"2006-01-01" + "subject_id": 2, + "cohort_start_date": "2006-01-01", + "cohort_end_date": "2006-01-01" } ] } \ No newline at end of file diff --git a/src/test/resources/corelatedcriteria/distinctVisit_PREP.json b/src/test/resources/corelatedcriteria/distinctVisit_PREP.json index adbdb532..b6fb6186 100644 --- a/src/test/resources/corelatedcriteria/distinctVisit_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctVisit_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime":"2000-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 1 }, @@ -29,6 +30,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 2 }, @@ -37,6 +39,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-02", + "condition_start_datetime":"2005-01-02 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 2 }, @@ -45,6 +48,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 3 }, @@ -53,6 +57,7 @@ "person_id":2, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 09:00:00", "condition_type_concept_id":0, "visit_occurrence_id": 4 } diff --git a/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json b/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json index ae85c790..8e8b9f2c 100644 --- a/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json +++ b/src/test/resources/corelatedcriteria/simpleInclusion_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime":"2000-01-01 01:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +29,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 01:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +37,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-02-01", + "condition_start_datetime":"2005-02-01 01:00:00", "condition_type_concept_id":0 }, { @@ -42,6 +45,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-03-01", + "condition_start_datetime":"2005-03-01 01:00:00", "condition_type_concept_id":0 }, { @@ -49,6 +53,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-04-01", + "condition_start_datetime":"2005-04-01 01:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json b/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json index a0f1dee9..63fe4bbc 100644 --- a/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json +++ b/src/test/resources/criteria/conditionOccurrenceDateOffset_PREP.json @@ -21,6 +21,7 @@ "person_id":1, "condition_concept_id":1, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,7 +29,9 @@ "person_id":1, "condition_concept_id":1, "condition_start_date":"2000-06-01", + "condition_start_datetime": "2000-06-01 00:00:00", "condition_end_date":"2000-07-01", + "condition_end_datetime": "2000-07-01 00:00:00", "condition_type_concept_id":0 }, { @@ -36,6 +39,7 @@ "person_id":1, "condition_concept_id":1, "condition_start_date":"2000-12-31", + "condition_start_datetime": "2000-12-31 00:00:00", "condition_type_concept_id":0 }, { @@ -43,6 +47,7 @@ "person_id":2, "condition_concept_id":1, "condition_start_date":"2000-01-01", + "condition_start_datetime": "2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -50,6 +55,7 @@ "person_id":2, "condition_concept_id":1, "condition_start_date":"2000-12-31", + "condition_start_datetime": "2000-12-31 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/ddl/cdm_v5.0.sql b/src/test/resources/ddl/cdm_v5.0.sql index eb9ce9af..e4771e5f 100644 --- a/src/test/resources/ddl/cdm_v5.0.sql +++ b/src/test/resources/ddl/cdm_v5.0.sql @@ -119,8 +119,8 @@ CREATE TABLE @schemaName.attribute_definition ( ) ; -CREATE TABLE @schemaName.cdm_source - ( +CREATE TABLE @schemaName.cdm_source + ( cdm_source_name VARCHAR(255) NOT NULL, cdm_source_abbreviation VARCHAR(25) NULL, cdm_holder VARCHAR(255) NULL, @@ -131,46 +131,46 @@ CREATE TABLE @schemaName.cdm_source cdm_release_date DATE NULL, cdm_version VARCHAR(10) NULL, vocabulary_version VARCHAR(20) NULL - ) + ) ; -CREATE TABLE @schemaName.person +CREATE TABLE @schemaName.person ( - person_id INTEGER NOT NULL , - gender_concept_id INTEGER NOT NULL , - year_of_birth INTEGER NOT NULL , - month_of_birth INTEGER NULL, - day_of_birth INTEGER NULL, + person_id INTEGER NOT NULL , + gender_concept_id INTEGER NOT NULL , + year_of_birth INTEGER NOT NULL , + month_of_birth INTEGER NULL, + day_of_birth INTEGER NULL, time_of_birth VARCHAR(10) NULL, - race_concept_id INTEGER NOT NULL, - ethnicity_concept_id INTEGER NOT NULL, - location_id INTEGER NULL, - provider_id INTEGER NULL, - care_site_id INTEGER NULL, - person_source_value VARCHAR(50) NULL, + race_concept_id INTEGER NOT NULL, + ethnicity_concept_id INTEGER NOT NULL, + location_id INTEGER NULL, + provider_id INTEGER NULL, + care_site_id INTEGER NULL, + person_source_value VARCHAR(50) NULL, gender_source_value VARCHAR(50) NULL, - gender_source_concept_id INTEGER NULL, - race_source_value VARCHAR(50) NULL, - race_source_concept_id INTEGER NULL, + gender_source_concept_id INTEGER NULL, + race_source_value VARCHAR(50) NULL, + race_source_concept_id INTEGER NULL, ethnicity_source_value VARCHAR(50) NULL, ethnicity_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.observation_period - ( - observation_period_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - observation_period_start_date DATE NOT NULL , +CREATE TABLE @schemaName.observation_period + ( + observation_period_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + observation_period_start_date DATE NOT NULL , observation_period_end_date DATE NOT NULL , period_type_concept_id INTEGER NOT NULL - ) + ) ; CREATE TABLE @schemaName.specimen - ( + ( specimen_id INTEGER NOT NULL , person_id INTEGER NOT NULL , specimen_concept_id INTEGER NOT NULL , @@ -190,141 +190,142 @@ CREATE TABLE @schemaName.specimen ; -CREATE TABLE @schemaName.death - ( - person_id INTEGER NOT NULL , - death_date DATE NOT NULL , - death_type_concept_id INTEGER NOT NULL , - cause_concept_id INTEGER NULL , +CREATE TABLE @schemaName.death + ( + person_id INTEGER NOT NULL , + death_date DATE NOT NULL , + death_type_concept_id INTEGER NOT NULL , + cause_concept_id INTEGER NULL , cause_source_value VARCHAR(50) NULL, cause_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.visit_occurrence - ( - visit_occurrence_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - visit_concept_id INTEGER NOT NULL , - visit_start_date DATE NOT NULL , +CREATE TABLE @schemaName.visit_occurrence + ( + visit_occurrence_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + visit_concept_id INTEGER NOT NULL , + visit_start_date DATE NOT NULL , visit_start_time VARCHAR(10) NULL , visit_end_date DATE NOT NULL , - visit_end_time VARCHAR(10) NULL , + visit_end_time VARCHAR(10) NULL , visit_type_concept_id INTEGER NOT NULL , provider_id INTEGER NULL, - care_site_id INTEGER NULL, + care_site_id INTEGER NULL, visit_source_value VARCHAR(50) NULL, visit_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.procedure_occurrence - ( - procedure_occurrence_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - procedure_concept_id INTEGER NOT NULL , - procedure_date DATE NOT NULL , +CREATE TABLE @schemaName.procedure_occurrence + ( + procedure_occurrence_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + procedure_concept_id INTEGER NOT NULL , + procedure_date DATE NOT NULL , procedure_type_concept_id INTEGER NOT NULL , modifier_concept_id INTEGER NULL , - quantity INTEGER NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + quantity INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , procedure_source_value VARCHAR(50) NULL , procedure_source_concept_id INTEGER NULL , qualifier_source_value VARCHAR(50) NULL - ) -; - - -CREATE TABLE @schemaName.drug_exposure - ( - drug_exposure_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - drug_concept_id INTEGER NOT NULL , - drug_exposure_start_date DATE NOT NULL , - drug_exposure_end_date DATE NULL , - drug_type_concept_id INTEGER NOT NULL , - stop_reason VARCHAR(20) NULL , - refills INTEGER NULL , - quantity NUMERIC NULL , - days_supply INTEGER NULL , - sig TEXT NULL , + ) +; + + +CREATE TABLE @schemaName.drug_exposure + ( + drug_exposure_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + drug_concept_id INTEGER NOT NULL , + drug_exposure_start_date DATE NOT NULL , + drug_exposure_end_date DATE NULL , + drug_type_concept_id INTEGER NOT NULL , + stop_reason VARCHAR(20) NULL , + refills INTEGER NULL , + quantity NUMERIC NULL , + days_supply INTEGER NULL , + sig TEXT NULL , route_concept_id INTEGER NULL , effective_drug_dose NUMERIC NULL , dose_unit_concept_id INTEGER NULL , lot_number VARCHAR(50) NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , drug_source_value VARCHAR(50) NULL , drug_source_concept_id INTEGER NULL , route_source_value VARCHAR(50) NULL , dose_unit_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.device_exposure - ( - device_exposure_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - device_concept_id INTEGER NOT NULL , - device_exposure_start_date DATE NOT NULL , - device_exposure_end_date DATE NULL , - device_type_concept_id INTEGER NOT NULL , +CREATE TABLE @schemaName.device_exposure + ( + device_exposure_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + device_concept_id INTEGER NOT NULL , + device_exposure_start_date DATE NOT NULL , + device_exposure_end_date DATE NULL , + device_type_concept_id INTEGER NOT NULL , unique_device_id VARCHAR(50) NULL , quantity INTEGER NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , device_source_value VARCHAR(100) NULL , device_source_concept_id INTEGER NULL - ) -; - -CREATE TABLE @schemaName.condition_occurrence - ( - condition_occurrence_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - condition_concept_id INTEGER NOT NULL , - condition_start_date DATE NOT NULL , - condition_end_date DATE NULL , - condition_type_concept_id INTEGER NOT NULL , - stop_reason VARCHAR(20) NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + ) +; + +CREATE TABLE @schemaName.condition_occurrence + ( + condition_occurrence_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + condition_concept_id INTEGER NOT NULL , + condition_start_date DATE NOT NULL , + condition_start_datetime TIMESTAMP NOT NULL , + condition_end_date DATE NULL , + condition_end_datetime TIMESTAMP NULL , + condition_type_concept_id INTEGER NOT NULL , + stop_reason VARCHAR(20) NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , condition_source_value VARCHAR(50) NULL , condition_source_concept_id INTEGER NULL - ) -; + ); -CREATE TABLE @schemaName.measurement - ( - measurement_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - measurement_concept_id INTEGER NOT NULL , - measurement_date DATE NOT NULL , +CREATE TABLE @schemaName.measurement + ( + measurement_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + measurement_concept_id INTEGER NOT NULL , + measurement_date DATE NOT NULL , measurement_time VARCHAR(10) NULL , measurement_type_concept_id INTEGER NOT NULL , - operator_concept_id INTEGER NULL , - value_as_number NUMERIC NULL , - value_as_concept_id INTEGER NULL , - unit_concept_id INTEGER NULL , - range_low NUMERIC NULL , - range_high NUMERIC NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , - measurement_source_value VARCHAR(50) NULL , + operator_concept_id INTEGER NULL , + value_as_number NUMERIC NULL , + value_as_concept_id INTEGER NULL , + unit_concept_id INTEGER NULL , + range_low NUMERIC NULL , + range_high NUMERIC NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , + measurement_source_value VARCHAR(50) NULL , measurement_source_concept_id INTEGER NULL , unit_source_value VARCHAR(50) NULL , value_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.note - ( - note_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , +CREATE TABLE @schemaName.note + ( + note_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , note_date DATE NOT NULL , note_time VARCHAR(10) NULL , note_type_concept_id INTEGER NOT NULL , @@ -332,36 +333,36 @@ CREATE TABLE @schemaName.note provider_id INTEGER NULL , visit_occurrence_id INTEGER NULL , note_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.observation - ( - observation_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - observation_concept_id INTEGER NOT NULL , - observation_date DATE NOT NULL , - observation_time VARCHAR(10) NULL , - observation_type_concept_id INTEGER NOT NULL , - value_as_number NUMERIC NULL , - value_as_string VARCHAR(60) NULL , - value_as_concept_id INTEGER NULL , +CREATE TABLE @schemaName.observation + ( + observation_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + observation_concept_id INTEGER NOT NULL , + observation_date DATE NOT NULL , + observation_time VARCHAR(10) NULL , + observation_type_concept_id INTEGER NOT NULL , + value_as_number NUMERIC NULL , + value_as_string VARCHAR(60) NULL , + value_as_concept_id INTEGER NULL , qualifier_concept_id INTEGER NULL , - unit_concept_id INTEGER NULL , - provider_id INTEGER NULL , - visit_occurrence_id INTEGER NULL , + unit_concept_id INTEGER NULL , + provider_id INTEGER NULL , + visit_occurrence_id INTEGER NULL , observation_source_value VARCHAR(50) NULL , - observation_source_concept_id INTEGER NULL , + observation_source_concept_id INTEGER NULL , unit_source_value VARCHAR(50) NULL , qualifier_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.fact_relationship - ( - domain_concept_id_1 INTEGER NOT NULL , +CREATE TABLE @schemaName.fact_relationship + ( + domain_concept_id_1 INTEGER NOT NULL , fact_id_1 INTEGER NOT NULL , domain_concept_id_2 INTEGER NOT NULL , fact_id_2 INTEGER NOT NULL , @@ -369,187 +370,187 @@ CREATE TABLE @schemaName.fact_relationship ) ; -CREATE TABLE @schemaName.location - ( - location_id INTEGER NOT NULL , - address_1 VARCHAR(50) NULL , - address_2 VARCHAR(50) NULL , - city VARCHAR(50) NULL , - state VARCHAR(2) NULL , - zip VARCHAR(9) NULL , - county VARCHAR(20) NULL , +CREATE TABLE @schemaName.location + ( + location_id INTEGER NOT NULL , + address_1 VARCHAR(50) NULL , + address_2 VARCHAR(50) NULL , + city VARCHAR(50) NULL , + state VARCHAR(2) NULL , + zip VARCHAR(9) NULL , + county VARCHAR(20) NULL , location_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.care_site - ( - care_site_id INTEGER NOT NULL , +CREATE TABLE @schemaName.care_site + ( + care_site_id INTEGER NOT NULL , care_site_name VARCHAR(255) NULL , place_of_service_concept_id INTEGER NULL , - location_id INTEGER NULL , - care_site_source_value VARCHAR(50) NULL , + location_id INTEGER NULL , + care_site_source_value VARCHAR(50) NULL , place_of_service_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.provider - ( +CREATE TABLE @schemaName.provider + ( provider_id INTEGER NOT NULL , - provider_name VARCHAR(255) NULL , - NPI VARCHAR(20) NULL , - DEA VARCHAR(20) NULL , - specialty_concept_id INTEGER NULL , - care_site_id INTEGER NULL , + provider_name VARCHAR(255) NULL , + NPI VARCHAR(20) NULL , + DEA VARCHAR(20) NULL , + specialty_concept_id INTEGER NULL , + care_site_id INTEGER NULL , year_of_birth INTEGER NULL , gender_concept_id INTEGER NULL , - provider_source_value VARCHAR(50) NULL , + provider_source_value VARCHAR(50) NULL , specialty_source_value VARCHAR(50) NULL , - specialty_source_concept_id INTEGER NULL , + specialty_source_concept_id INTEGER NULL , gender_source_value VARCHAR(50) NULL , gender_source_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.payer_plan_period - ( - payer_plan_period_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - payer_plan_period_start_date DATE NOT NULL , - payer_plan_period_end_date DATE NOT NULL , - payer_source_value VARCHAR (50) NULL , - plan_source_value VARCHAR (50) NULL , - family_source_value VARCHAR (50) NULL - ) +CREATE TABLE @schemaName.payer_plan_period + ( + payer_plan_period_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + payer_plan_period_start_date DATE NOT NULL , + payer_plan_period_end_date DATE NOT NULL , + payer_source_value VARCHAR (50) NULL , + plan_source_value VARCHAR (50) NULL , + family_source_value VARCHAR (50) NULL + ) ; -CREATE TABLE @schemaName.visit_cost - ( - visit_cost_id INTEGER NOT NULL , - visit_occurrence_id INTEGER NOT NULL , +CREATE TABLE @schemaName.visit_cost + ( + visit_cost_id INTEGER NOT NULL , + visit_occurrence_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , - total_paid NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , + total_paid NUMERIC NULL , payer_plan_period_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.procedure_cost - ( - procedure_cost_id INTEGER NOT NULL , - procedure_occurrence_id INTEGER NOT NULL , +CREATE TABLE @schemaName.procedure_cost + ( + procedure_cost_id INTEGER NOT NULL , + procedure_occurrence_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , total_paid NUMERIC NULL , - revenue_code_concept_id INTEGER NULL , + revenue_code_concept_id INTEGER NULL , payer_plan_period_id INTEGER NULL , revenue_code_source_value VARCHAR(50) NULL - ) + ) ; -CREATE TABLE @schemaName.drug_cost +CREATE TABLE @schemaName.drug_cost ( - drug_cost_id INTEGER NOT NULL , - drug_exposure_id INTEGER NOT NULL , + drug_cost_id INTEGER NOT NULL , + drug_exposure_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , - total_paid NUMERIC NULL , - ingredient_cost NUMERIC NULL , - dispensing_fee NUMERIC NULL , - average_wholesale_price NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , + total_paid NUMERIC NULL , + ingredient_cost NUMERIC NULL , + dispensing_fee NUMERIC NULL , + average_wholesale_price NUMERIC NULL , payer_plan_period_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.device_cost +CREATE TABLE @schemaName.device_cost ( - device_cost_id INTEGER NOT NULL , - device_exposure_id INTEGER NOT NULL , + device_cost_id INTEGER NOT NULL , + device_exposure_id INTEGER NOT NULL , currency_concept_id INTEGER NULL , - paid_copay NUMERIC NULL , - paid_coinsurance NUMERIC NULL , - paid_toward_deductible NUMERIC NULL , - paid_by_payer NUMERIC NULL , - paid_by_coordination_benefits NUMERIC NULL , - total_out_of_pocket NUMERIC NULL , - total_paid NUMERIC NULL , + paid_copay NUMERIC NULL , + paid_coinsurance NUMERIC NULL , + paid_toward_deductible NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_coordination_benefits NUMERIC NULL , + total_out_of_pocket NUMERIC NULL , + total_paid NUMERIC NULL , payer_plan_period_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.cohort - ( - cohort_definition_id INTEGER NOT NULL , +CREATE TABLE @schemaName.cohort + ( + cohort_definition_id INTEGER NOT NULL , subject_id INTEGER NOT NULL , - cohort_start_date DATE NOT NULL , + cohort_start_date DATE NOT NULL , cohort_end_date DATE NOT NULL - ) + ) ; -CREATE TABLE @schemaName.cohort_attribute - ( - cohort_definition_id INTEGER NOT NULL , - cohort_start_date DATE NOT NULL , - cohort_end_date DATE NOT NULL , - subject_id INTEGER NOT NULL , +CREATE TABLE @schemaName.cohort_attribute + ( + cohort_definition_id INTEGER NOT NULL , + cohort_start_date DATE NOT NULL , + cohort_end_date DATE NOT NULL , + subject_id INTEGER NOT NULL , attribute_definition_id INTEGER NOT NULL , value_as_number NUMERIC NULL , value_as_concept_id INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.drug_era - ( - drug_era_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - drug_concept_id INTEGER NOT NULL , - drug_era_start_date DATE NOT NULL , - drug_era_end_date DATE NOT NULL , +CREATE TABLE @schemaName.drug_era + ( + drug_era_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + drug_concept_id INTEGER NOT NULL , + drug_era_start_date DATE NOT NULL , + drug_era_end_date DATE NOT NULL , drug_exposure_count INTEGER NULL , gap_days INTEGER NULL - ) + ) ; -CREATE TABLE @schemaName.dose_era +CREATE TABLE @schemaName.dose_era ( - dose_era_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - drug_concept_id INTEGER NOT NULL , + dose_era_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + drug_concept_id INTEGER NOT NULL , unit_concept_id INTEGER NOT NULL , dose_value NUMERIC NOT NULL , - dose_era_start_date DATE NOT NULL , - dose_era_end_date DATE NOT NULL - ) + dose_era_start_date DATE NOT NULL , + dose_era_end_date DATE NOT NULL + ) ; -CREATE TABLE @schemaName.condition_era - ( - condition_era_id INTEGER NOT NULL , - person_id INTEGER NOT NULL , - condition_concept_id INTEGER NOT NULL , - condition_era_start_date DATE NOT NULL , - condition_era_end_date DATE NOT NULL , +CREATE TABLE @schemaName.condition_era + ( + condition_era_id INTEGER NOT NULL , + person_id INTEGER NOT NULL , + condition_concept_id INTEGER NOT NULL , + condition_era_start_date DATE NOT NULL , + condition_era_end_date DATE NOT NULL , condition_occurrence_count INTEGER NULL - ) + ) ; diff --git a/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json b/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json index 287f1e2a..4c687fc7 100644 --- a/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json +++ b/src/test/resources/negativecontrols/firstOccurrence/firstOccurrenceTest_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-06-07", + "condition_start_datetime": "2000-06-07 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-09-01", + "condition_start_datetime": "2000-09-01 00:00:00", "condition_type_concept_id":0 } ] diff --git a/src/test/resources/printfriendly/allAttributes.json b/src/test/resources/printfriendly/allAttributes.json index d52773ef..1c5573d4 100644 --- a/src/test/resources/printfriendly/allAttributes.json +++ b/src/test/resources/printfriendly/allAttributes.json @@ -70,18 +70,21 @@ }, "StartWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -141,7 +144,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -173,7 +177,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -318,7 +323,8 @@ "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -351,7 +357,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -373,7 +380,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -454,7 +462,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -543,7 +552,8 @@ "Coeff": -1 }, "End": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -565,7 +575,8 @@ "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -672,11 +683,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -711,7 +724,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -795,7 +809,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -816,7 +831,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/anyCondition.json b/src/test/resources/printfriendly/anyCondition.json index 9ce0fa02..73ef7919 100644 --- a/src/test/resources/printfriendly/anyCondition.json +++ b/src/test/resources/printfriendly/anyCondition.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/printfriendly/censorCriteria.json b/src/test/resources/printfriendly/censorCriteria.json index 5695f9a1..e2948c49 100644 --- a/src/test/resources/printfriendly/censorCriteria.json +++ b/src/test/resources/printfriendly/censorCriteria.json @@ -324,11 +324,13 @@ }, "StartWindow": { "Start": { - "Days": 365, + "TimeUnitValue": 365, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -356,7 +358,8 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/conceptSet_simple.json b/src/test/resources/printfriendly/conceptSet_simple.json index 4b4d6399..de95aee1 100644 --- a/src/test/resources/printfriendly/conceptSet_simple.json +++ b/src/test/resources/printfriendly/conceptSet_simple.json @@ -74,7 +74,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/conditionEra.json b/src/test/resources/printfriendly/conditionEra.json index f1742911..9c7865f5 100644 --- a/src/test/resources/printfriendly/conditionEra.json +++ b/src/test/resources/printfriendly/conditionEra.json @@ -37,22 +37,26 @@ }, "StartWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false }, "EndWindow": { "Start": { - "Days": 7, + "TimeUnitValue": 7, + "TimeUnit": "day", "Coeff": 1 }, "End": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": true @@ -136,11 +140,13 @@ }, "StartWindow": { "Start": { - "Days": 90, + "TimeUnitValue": 90, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -159,7 +165,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/conditionOccurrence.json b/src/test/resources/printfriendly/conditionOccurrence.json index ff1aea70..7cd95ca8 100644 --- a/src/test/resources/printfriendly/conditionOccurrence.json +++ b/src/test/resources/printfriendly/conditionOccurrence.json @@ -52,7 +52,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -204,7 +205,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": true @@ -242,7 +244,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -261,7 +264,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/continuousObservation_none.json b/src/test/resources/printfriendly/continuousObservation_none.json index 2cfada2c..9d63f1b4 100644 --- a/src/test/resources/printfriendly/continuousObservation_none.json +++ b/src/test/resources/printfriendly/continuousObservation_none.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/continuousObservation_post.json b/src/test/resources/printfriendly/continuousObservation_post.json index 5a2432f1..05538e32 100644 --- a/src/test/resources/printfriendly/continuousObservation_post.json +++ b/src/test/resources/printfriendly/continuousObservation_post.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/continuousObservation_prior.json b/src/test/resources/printfriendly/continuousObservation_prior.json index 7b578f9d..774f4a73 100644 --- a/src/test/resources/printfriendly/continuousObservation_prior.json +++ b/src/test/resources/printfriendly/continuousObservation_prior.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/continuousObservation_priorpost.json b/src/test/resources/printfriendly/continuousObservation_priorpost.json index 5c37ec76..56abf230 100644 --- a/src/test/resources/printfriendly/continuousObservation_priorpost.json +++ b/src/test/resources/printfriendly/continuousObservation_priorpost.json @@ -24,7 +24,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/countCriteria.json b/src/test/resources/printfriendly/countCriteria.json index dc46760f..d5561bd3 100644 --- a/src/test/resources/printfriendly/countCriteria.json +++ b/src/test/resources/printfriendly/countCriteria.json @@ -30,11 +30,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -76,7 +78,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -97,7 +100,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -236,7 +240,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -267,7 +272,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -297,7 +303,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -330,7 +337,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -348,7 +356,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -375,11 +384,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -400,7 +411,8 @@ "Coeff": -1 }, "End": { - "Days": 31, + "TimeUnitValue": 31, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -421,7 +433,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/printfriendly/countDistinctCriteria.json b/src/test/resources/printfriendly/countDistinctCriteria.json index 69a6d669..45bed9c7 100644 --- a/src/test/resources/printfriendly/countDistinctCriteria.json +++ b/src/test/resources/printfriendly/countDistinctCriteria.json @@ -30,11 +30,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -77,7 +79,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -104,7 +107,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -127,7 +131,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -168,7 +173,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/customEraExit.json b/src/test/resources/printfriendly/customEraExit.json index 06015aa7..e74f2963 100644 --- a/src/test/resources/printfriendly/customEraExit.json +++ b/src/test/resources/printfriendly/customEraExit.json @@ -50,7 +50,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/dateOffset.json b/src/test/resources/printfriendly/dateOffset.json index 0ac9dd93..f52faaeb 100644 --- a/src/test/resources/printfriendly/dateOffset.json +++ b/src/test/resources/printfriendly/dateOffset.json @@ -42,13 +42,15 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 7 + "OffsetUnit": "day", + "OffsetUnitValue": 7 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/death.json b/src/test/resources/printfriendly/death.json index a1927e16..4c232614 100644 --- a/src/test/resources/printfriendly/death.json +++ b/src/test/resources/printfriendly/death.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -173,7 +174,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/deviceExposure.json b/src/test/resources/printfriendly/deviceExposure.json index 0bb9432b..3f6a4e10 100644 --- a/src/test/resources/printfriendly/deviceExposure.json +++ b/src/test/resources/printfriendly/deviceExposure.json @@ -66,7 +66,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -180,7 +181,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -215,11 +217,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -238,7 +242,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/doseEra.json b/src/test/resources/printfriendly/doseEra.json index ccbc833e..1c1493d4 100644 --- a/src/test/resources/printfriendly/doseEra.json +++ b/src/test/resources/printfriendly/doseEra.json @@ -38,11 +38,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -60,11 +62,13 @@ }, "StartWindow": { "Start": { - "Days": 30, + "TimeUnitValue": 30, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -168,11 +172,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -190,11 +196,13 @@ }, "StartWindow": { "Start": { - "Days": 60, + "TimeUnitValue": 60, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -232,7 +240,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -257,7 +266,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -276,7 +286,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/drugEra.json b/src/test/resources/printfriendly/drugEra.json index fc27a96f..6f8247bc 100644 --- a/src/test/resources/printfriendly/drugEra.json +++ b/src/test/resources/printfriendly/drugEra.json @@ -41,7 +41,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -158,7 +159,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -180,7 +182,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/drugExposure.json b/src/test/resources/printfriendly/drugExposure.json index fb50b1e2..b2ad53e5 100644 --- a/src/test/resources/printfriendly/drugExposure.json +++ b/src/test/resources/printfriendly/drugExposure.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -58,11 +59,13 @@ }, "StartWindow": { "Start": { - "Days": 14, + "TimeUnitValue": 14, + "TimeUnit": "day", "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -247,7 +250,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/measurement.json b/src/test/resources/printfriendly/measurement.json index 505a8988..0f5f0bc7 100644 --- a/src/test/resources/printfriendly/measurement.json +++ b/src/test/resources/printfriendly/measurement.json @@ -41,7 +41,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -59,7 +60,8 @@ }, "StartWindow": { "Start": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -261,7 +263,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/noCensorCriteria.json b/src/test/resources/printfriendly/noCensorCriteria.json index 9c224c31..dd045db5 100644 --- a/src/test/resources/printfriendly/noCensorCriteria.json +++ b/src/test/resources/printfriendly/noCensorCriteria.json @@ -37,8 +37,8 @@ } ], "ObservationWindow":{ - "PriorDays":0, - "PostDays":0 + "PriorDays": 0, + "PostDays": 0 }, "PrimaryCriteriaLimit":{ "Type":"First" @@ -398,6 +398,7 @@ ], "CollapseSettings":{ "CollapseType":"ERA", - "EraPad":0 + "EraPadUnit": "days", + "EraPadUnitValue": 0 } } \ No newline at end of file diff --git a/src/test/resources/printfriendly/nullCodesetId.json b/src/test/resources/printfriendly/nullCodesetId.json index b2e94068..03ec724a 100644 --- a/src/test/resources/printfriendly/nullCodesetId.json +++ b/src/test/resources/printfriendly/nullCodesetId.json @@ -376,7 +376,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {} } diff --git a/src/test/resources/printfriendly/observation.json b/src/test/resources/printfriendly/observation.json index b72d3886..561af4c1 100644 --- a/src/test/resources/printfriendly/observation.json +++ b/src/test/resources/printfriendly/observation.json @@ -41,7 +41,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -216,7 +217,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/observationPeriod_1.json b/src/test/resources/printfriendly/observationPeriod_1.json index fb96e549..a55605eb 100644 --- a/src/test/resources/printfriendly/observationPeriod_1.json +++ b/src/test/resources/printfriendly/observationPeriod_1.json @@ -35,7 +35,8 @@ }, "StartWindow": { "Start": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -119,7 +120,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/procedureOccurrence.json b/src/test/resources/printfriendly/procedureOccurrence.json index d29c1e89..5118c603 100644 --- a/src/test/resources/printfriendly/procedureOccurrence.json +++ b/src/test/resources/printfriendly/procedureOccurrence.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -190,7 +191,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/specimen.json b/src/test/resources/printfriendly/specimen.json index 6726d52d..f577d458 100644 --- a/src/test/resources/printfriendly/specimen.json +++ b/src/test/resources/printfriendly/specimen.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 1, + "TimeUnitValue": 1, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -175,7 +176,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/visit.json b/src/test/resources/printfriendly/visit.json index c1486b3e..a6296603 100644 --- a/src/test/resources/printfriendly/visit.json +++ b/src/test/resources/printfriendly/visit.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -176,7 +177,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json index a3dd827b..211b685c 100644 --- a/src/test/resources/printfriendly/visitDetail.json +++ b/src/test/resources/printfriendly/visitDetail.json @@ -40,7 +40,8 @@ "Coeff": -1 }, "End": { - "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -108,7 +109,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0 + "EraPadUnit": "day", + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json b/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json index 9878b393..2b315b06 100644 --- a/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json +++ b/src/test/resources/windowcriteria/windowConditionOccurrence_PREP.json @@ -14,6 +14,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2000-01-01", + "condition_start_datetime":"2000-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -21,6 +22,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-01-01", + "condition_start_datetime":"2005-01-01 00:00:00", "condition_type_concept_id":0 }, { @@ -28,6 +30,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-02-01", + "condition_start_datetime":"2005-02-01 00:00:00", "condition_type_concept_id":0 }, { @@ -35,6 +38,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-03-01", + "condition_start_datetime":"2005-03-01 00:00:00", "condition_type_concept_id":0 }, { @@ -42,6 +46,7 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2005-04-01", + "condition_start_datetime":"2005-04-01 00:00:00", "condition_type_concept_id":0 } ], From 55b2eefeaf5d76ddbba48eba5f7892688d9fbd60 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Fri, 29 Dec 2023 20:18:16 +0700 Subject: [PATCH 10/25] update --- .../circe/check/checkers/Comparisons.java | 45 +++++++------- .../checkers/ExitCriteriaDaysOffsetCheck.java | 25 +++++--- .../checkers/FirstTimeInHistoryCheck.java | 7 ++- .../circe/check/checkers/RangeCheck.java | 26 ++++++--- .../check/checkers/TimePatternCheck.java | 25 +++++--- .../CohortExpressionQueryBuilder.java | 58 ++++++++++++------- .../cohortdefinition/CollapseSettings.java | 4 +- .../cohortdefinition/DateOffsetStrategy.java | 5 +- .../circe/cohortdefinition/IntervalUnit.java | 1 - .../ConditionOccurrenceSqlBuilder.java | 10 +--- .../printfriendly/cohortExpression.ftl | 4 ++ .../printfriendly/criteriaTypes.ftl | 49 +++++++++++++--- .../printfriendly/endStrategyTypes.ftl | 7 ++- .../printfriendly/inputTypes.ftl | 40 ++++++++++--- .../sql/dateOffsetStrategy.sql | 23 ++++++-- .../cohortdefinition/sql/generateCohort.sql | 10 +++- 16 files changed, 230 insertions(+), 109 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 82ffe189..697b746e 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -20,9 +20,7 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -31,6 +29,12 @@ public class Comparisons { + private static final Map TIME_UNIT_CONVERSION = new HashMap<>(); + static { + TIME_UNIT_CONVERSION.put(IntervalUnit.HOUR.getName(), 60 * 60); + TIME_UNIT_CONVERSION.put(IntervalUnit.MINUTE.getName(), 60); + } + public static Boolean startIsGreaterThanEnd(NumericRange r) { return Objects.nonNull(r.value) && Objects.nonNull(r.extent) && r.value.intValue() > r.extent.intValue(); @@ -101,31 +105,26 @@ public static Predicate compare(Concept source) { .append(concept.vocabularyId, source.vocabularyId) .build(); } - public static int compareTo(ObservationFilter filter, Window window) { - int range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; - int range2Start = getTimeInSeconds(window.start); - int range2End = getTimeInSeconds(window.end); + int range1 , range2Start , range2End; + if(Objects.nonNull(window.start) && Objects.nonNull(window.start.days)){ + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + }else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); + } return range1 - (range2End - range2Start); } - private static int getTimeInSeconds(Window.Endpoint endpoint) { - if (Objects.isNull(endpoint)) { - return 0; + return Optional.ofNullable(endpoint) + .map(ep -> { + int convertRate = TIME_UNIT_CONVERSION.getOrDefault(ep.timeUnit, 1); + return Objects.nonNull(ep.timeUnitValue) ? ep.coeff * ep.timeUnitValue * convertRate : 0; + }).orElse(0); } - int convertRate; - if (endpoint.timeUnit.equals(IntervalUnit.DAY.getName())) { - convertRate = 24 * 60 * 60; - } else if (endpoint.timeUnit.equals(IntervalUnit.HOUR.getName())) { - convertRate = 60 * 60; - } else if (endpoint.timeUnit.equals(IntervalUnit.MINUTE.getName())) { - convertRate = 60; - } else convertRate = 1; - return Objects.nonNull(endpoint.timeUnitValue) - ? endpoint.coeff * endpoint.timeUnitValue * convertRate - : 0; - } - public static boolean compare(Criteria c1, Criteria c2) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index 1264be55..3d814885 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -18,14 +18,17 @@ package org.ohdsi.circe.check.checkers; -import static org.ohdsi.circe.check.operations.Operations.match; -import static org.ohdsi.circe.cohortdefinition.DateOffsetStrategy.DateField.StartDate; - -import java.util.Objects; import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.cohortdefinition.CohortExpression; import org.ohdsi.circe.cohortdefinition.DateOffsetStrategy; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static org.ohdsi.circe.check.operations.Operations.match; +import static org.ohdsi.circe.cohortdefinition.DateOffsetStrategy.DateField.StartDate; + public class ExitCriteriaDaysOffsetCheck extends BaseCheck { private static final String DAYS_OFFSET_WARNING = "Cohort Exit criteria: %ss offset from start date should be greater than 0"; @@ -40,9 +43,15 @@ protected WarningSeverity defineSeverity() { protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) - .isA(DateOffsetStrategy.class) - .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) - .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); + .isA(DateOffsetStrategy.class) + .then(s -> match((DateOffsetStrategy)s) + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offset || 0 == dateOffsetStrategy.offsetUnitValue)) + .then(dateOffsetStrategy -> { + if(0 == dateOffsetStrategy.offset){ + reporter.add(String.format(DAYS_OFFSET_WARNING), "Days"); + }else { + reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)); + } + })); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 95f2109f..3c6db7c9 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -43,8 +43,11 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.timeUnitValue != null) || (c.startWindow.end != null - && c.startWindow.end.timeUnitValue != null))) + && c.startWindow.start.days != null) || (c.startWindow.end != null + && c.startWindow.end.days != null)) + || ((c.startWindow.start.timeUnitValue != null) + && (c.startWindow.end != null) + && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index 6b927308..01d4cdbd 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -25,6 +25,7 @@ import org.ohdsi.circe.cohortdefinition.Window; import java.util.Objects; +import java.util.Optional; public class RangeCheck extends BaseValueCheck { private static final String NEGATIVE_VALUE_ERROR = "Time window in criteria \"%s\" has negative value %d at %s"; @@ -52,15 +53,24 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep } private void checkWindow(Window window, WarningReporter reporter, String name) { + Optional.ofNullable(window) + .map(w -> checkEndpoint(w, name, "start")) + .ifPresent(reporter::add); - if (Objects.nonNull(window)) { - if (Objects.nonNull(window.start) && Objects.nonNull(window.start.timeUnitValue) && window.start.timeUnitValue < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.start.timeUnitValue, "start"); - } - if (Objects.nonNull(window.end) && Objects.nonNull(window.end.timeUnitValue) && window.end.timeUnitValue < 0) { - reporter.add(NEGATIVE_VALUE_ERROR, name, window.end.timeUnitValue, "end"); - } - } + Optional.ofNullable(window) + .map(w -> checkEndpoint(w, name, "end")) + .ifPresent(reporter::add); + } + + private String checkEndpoint(Window window, String name, String endpointType) { + boolean hasValid = Objects.nonNull(window.start) && Objects.nonNull(window.start.days < 0 ? window.start.days : window.start.timeUnitValue) && window.start.days < 0 ? window.start.days < 0 : window.start.timeUnitValue < 0; + return Optional.of(window) + .filter(w -> hasValid) + .map(w -> String.format(NEGATIVE_VALUE_ERROR, name, getEndpointValue(w.start), endpointType)) + .orElse(null); + } + private Object getEndpointValue(Window.Endpoint endpoint) { + return Objects.nonNull(endpoint.days) ? endpoint.days : endpoint.timeUnitValue; } private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java index 2a3c5407..561bf9b7 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java @@ -18,10 +18,7 @@ package org.ohdsi.circe.check.checkers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import org.ohdsi.circe.check.WarningSeverity; @@ -83,17 +80,29 @@ private String formatTimeWindow(TimeWindowInfo ti) { } private String formatDays(Window.Endpoint endpoint) { - return Objects.nonNull(endpoint.timeUnitValue) ? String.valueOf(endpoint.timeUnitValue) : "all"; + return Objects.nonNull(endpoint.days) ? String.valueOf(endpoint.days) : + Objects.nonNull(endpoint.timeUnitValue) ? String.valueOf(endpoint.timeUnitValue) : "all"; + } private String formatCoeff(Window.Endpoint endpoint) { return endpoint.coeff < 0 ? "before " : "after "; } + private Integer startDays(Window window) { - return Objects.nonNull(window) && Objects.nonNull(window.start) ? - (Objects.nonNull(window.start.timeUnitValue) ? window.start.timeUnitValue : 0) * window.start.coeff : 0; - } + return Optional.ofNullable(window) + .map(w -> w.start) + .map(start -> { + int coefficient = Optional.ofNullable(start.coeff).orElse(0); + return Optional.ofNullable(start.days) + .map(days -> days * coefficient) + .orElseGet(() -> Optional.ofNullable(start.timeUnitValue) + .map(timeUnitValue -> timeUnitValue * coefficient) + .orElse(0)); + }) + .orElse(0); +} class TimeWindowInfo { private String name; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 50071030..690d3675 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -20,9 +20,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; + +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang3.StringUtils; @@ -351,10 +350,12 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); -// resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); - resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); - resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); - + if(!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && expression.collapseSettings.eraPad != 0){ + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + }else{ + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); + } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByPersonQuery", getInclusionAnalysisQuery("#best_events", 1)); @@ -548,20 +549,25 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); + if (startWindow.start.days != null) { + startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); + } else if (startWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); } else { - startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + startExpression = checkObservationPeriod ? (startWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (startExpression != null) { clauses.add(String.format("%s >= %s", startEventDateExpression, startExpression)); } - if (startWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); + if (startWindow.end.days != null) { + endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); + } + else if (startWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); } else { - endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (endExpression != null) { @@ -575,20 +581,24 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit String endIndexDateExpression = (endWindow.useIndexEnd != null && endWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; // for backwards compatability, having a null endWindow.useIndexEnd means they SHOULD use the index end date. String endEventDateExpression = (endWindow.useEventEnd == null || endWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (endWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); + if (endWindow.start.days != null) { + startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); + } else if (endWindow.start.timeUnitValue != null) { + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); } else { - startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (startExpression != null) { clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); } - if (endWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); + if (endWindow.end.days != null) { + endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); + } else if (endWindow.end.timeUnitValue != null) { + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); } else { - endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (endExpression != null) { @@ -765,10 +775,13 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); - strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + if(strat.offset != 0){ + strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + }else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); - return strategySql; } @@ -793,4 +806,5 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { } // + } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 0850fb71..26170865 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -24,8 +24,10 @@ public class CollapseSettings { @JsonProperty("CollapseType") public CollapseType collapseType = CollapseType.ERA; + @JsonProperty("EraPad") + public int eraPad = 0; @JsonProperty("EraPadUnit") - public String eraPadUnit = IntervalUnit.DAY.getName(); + public String eraPadUnit; @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 8f610267..f2d0ed6b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -30,14 +30,15 @@ public enum DateField { StartDate, EndDate } - + @JsonProperty("Offset") + public int offset = 0; @JsonProperty("DateField") public DateField dateField = DateField.StartDate; @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; @JsonProperty("OffsetUnit") - public String offsetUnit = IntervalUnit.DAY.getName(); + public String offsetUnit; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java index e55b58e7..2a40b9cb 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -3,7 +3,6 @@ import jdk.nashorn.internal.objects.annotations.Getter; public enum IntervalUnit { - DAY("day"), HOUR("hour"), MINUTE("minute"), SECOND("second"); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 35defc8a..8ca6c2a5 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -4,12 +4,9 @@ import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; import org.ohdsi.circe.helper.ResourceHelper; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.text.SimpleDateFormat; +import java.util.*; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -65,7 +62,6 @@ protected String embedCodesetClause(String query, T criteria) { @Override protected String embedOrdinalExpression(String query, T criteria, List whereClauses) { - // first if (criteria.first != null && criteria.first == true) { whereClauses.add("C.ordinal = 1"); diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl index 16bc52e4..751887dd 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/cohortExpression.ftl @@ -64,7 +64,11 @@ ${cc?counter}. <@ct.Criteria c=cc/> ### Cohort Eras +<#if collapseSettings.eraPad?has_content && collapseSettings.eraPad != 0> Remaining events will be combined into cohort eras if they are within ${collapseSettings.eraPad} days of each other. +<#else > +Remaining events will be combined into cohort eras if they are within ${collapseSettings.eraPadUnitValue} ${collapseSettings.eraPadUnit}s of each other. + <#-- main template: end --> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index fcca4858..8f2cff2e 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -292,15 +292,46 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi <#macro CountCriteria countCriteria level=0 indexLabel="cohort entry">having <#if countCriteria.occurrence.type == 0 && countCriteria.occurrence.count == 0>no<#else>${inputTypes.getCountType(countCriteria)} ${countCriteria.occurrence.count}<#-- --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> -<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> -<#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> -<#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> -<#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if -windowParts?size gt 0>${windowParts?join(" and ")}<#if restrictParts?size gt 0>; <#if -restrictParts?size gt 0>${restrictParts?join(" and ")} - +<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]> + <#if countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??> + <#local temp> + <@inputTypes.Window countCriteria.startWindow indexLabel /> + + <#local windowParts+=[temp]> + + <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> +<#local temp> + <@inputTypes.Window countCriteria.endWindow indexLabel /> + + <#local windowParts+=[temp]> + + <#if countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??> + <#local temp> + <@inputTypes.Window countCriteria.startWindow indexLabel /> + + <#local windowParts+=[temp]> + + <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> + <#local temp> + <@inputTypes.Window countCriteria.endWindow indexLabel /> + + <#local windowParts+=[temp]> + +<#if countCriteria.restrictVisit!false> + <#local temp>at same visit as ${indexLabel} + + <#local restrictParts+=[temp]> + +<#if countCriteria.ignoreObservationPeriod!false> + <#local temp>allow events outside observation period + <#local restrictParts+=[temp]> + + <#if windowParts?size gt 0>${windowParts?join(" and ")} + <#if restrictParts?size gt 0>; + + <#if restrictParts?size gt 0>${restrictParts?join(" and ")} + + <#-- Demographic Criteria --> <#macro DemographicCriteria c level=0 indexLabel = "cohort entry"><#local attrs = []><#local temp><@AgeGenderCriteria ageAtStart=c.age!{} gender=c.gender!{} /><#if temp?has_content><#local attrs+=[temp]><#local diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index a71c1bf4..15e16a34 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -24,7 +24,12 @@ The person exits the cohort at the end of continuous observation. <#assign dateOffsetFieldOptions = [{"id": "StartDate", "name": "start date"}, {"id": "EndDate", "name": "end date"}]> <#macro DateOffsetStrategy s> -The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. + <#if s.offset?? && s.offset == "day" > + The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offset "day"/>. + <#else > + The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. + + <#macro CustomEraStrategy s> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index a18a704c..45c59c00 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -25,7 +25,7 @@ END Note!!!! --><#if range.op?ends_with("bt")> and <#if range.extent?has_content>${utils.formatDate(range.extent)}<#else>_empty_ <#-- ConceptList --> -<#macro ConceptList list quote="\""><#if (list?size > 0)><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if +<#macro ConceptList list quote="\""><#if (list?size > 0)><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if item?counter gt 1><#if item?counter == list?size> or <#else>, ${item}<#else>[none specified] <#-- ConceptSetSelection --> @@ -89,23 +89,45 @@ item?counter gt 1><#if item?counter == list?size> or <#else>, ${item <#function whichIndexPart useEnd><#if useEnd><#return "end date"><#else><#return "start date"> <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> -<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- ---><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- ---><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} +<#----> +<#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 > + anytime on or before ${indexLabel} + ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1> +<#if w.start.days??>in the ${w.start.days} days +<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} +<#----> +<#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} + <#-- User Defined Period --> <#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if -p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} +p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} + + <#-- Date Adjustment --> <#function toDatePart dateType><#if dateType == "START_DATE"><#return "start date"><#else><#return "end date"> -<#macro DateAdjustment da><#if +<#macro DateAdjustment da><#if da?has_content>starting ${(da.startOffset != 0)?then((da.startOffset?abs + " days " + (da.startOffset < 0)?then("before","after")), "on")}${(da.startWith != da.endWith)?then(" the event ${toDatePart(da.startWith)}","")}<#-- --> and ending ${(da.endOffset != 0)?then((da.endOffset?abs + " days " + (da.endOffset < 0)?then("before","after")), "on")} the event ${toDatePart(da.endWith)} \ No newline at end of file diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index b79289dd..cac7c35f 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,9 +1,20 @@ -- date offset strategy -select event_id, - person_id, - case - when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date - else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date +SELECT + event_id, + person_id, + CASE + WHEN @offset IS NOT NULL + THEN + CASE + WHEN DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date THEN op_end_date + ELSE DATEADD(@offsetUnit, @offsetUnitValue, @dateField) + END + ELSE + CASE + WHEN DATEADD(day, @offset, @dateField) > op_end_date THEN op_end_date + ELSE DATEADD(day, @offset, @dateField) + END + END AS end_date INTO #strategy_ends -from @eventTable; +FROM @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 0a873c67..8fad4578 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -62,8 +62,14 @@ from ( --cteEnds FROM #cohort_rows c JOIN ( -- cteEndDates SELECT - person_id - , DATEADD(@era_pad_unit, -1 * @era_pad, event_date) as end_date + person_id, + CASE + WHEN + @eraconstructorpad IS NOT NULL + THEN + DATEADD(day, -1 * @eraconstructorpad, event_date) + ELSE + DATEADD(@era_pad_unit, -1 * @era_pad, event_date) END AS end_date FROM ( SELECT From 806c85bac1c8bd917bb9a21c32293f54793ef152 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Thu, 4 Jan 2024 10:03:27 +0700 Subject: [PATCH 11/25] update/412023 --- .editorconfig | 2 +- .../circe/check/checkers/Comparisons.java | 20 ++-- .../checkers/ExitCriteriaDaysOffsetCheck.java | 10 +- .../checkers/FirstTimeInHistoryCheck.java | 10 +- .../circe/check/checkers/RangeCheck.java | 27 +++--- .../check/checkers/TimePatternCheck.java | 2 +- .../CohortExpressionQueryBuilder.java | 55 +++++++---- .../cohortdefinition/CollapseSettings.java | 6 +- .../cohortdefinition/CustomEraStrategy.java | 8 ++ .../cohortdefinition/DateOffsetStrategy.java | 4 +- .../circe/cohortdefinition/IntervalUnit.java | 1 + .../ohdsi/circe/cohortdefinition/Window.java | 8 +- .../ConditionOccurrenceSqlBuilder.java | 3 +- .../printfriendly/criteriaTypes.ftl | 48 ++-------- .../printfriendly/endStrategyTypes.ftl | 7 +- .../printfriendly/inputTypes.ftl | 66 ++++++------- .../sql/customEraStrategy.sql | 6 +- .../sql/dateOffsetStrategy.sql | 23 ++--- .../cohortdefinition/sql/generateCohort.sql | 11 +-- .../circe/check/checkers/ComparisonsTest.java | 95 ++++++++++++++++--- .../builders/CriteriaUtils.java | 3 +- .../printfriendly/PrintFriendlyTest.java | 6 +- .../additionalCriteriaCheckValueCorrect.json | 3 +- ...additionalCriteriaCheckValueIncorrect.json | 5 +- .../censoringEventCheckValueCorrect.json | 3 +- .../censoringEventCheckValueIncorrect.json | 3 +- .../conceptSetCriteriaCheckCorrect.json | 3 +- .../conceptSetCriteriaCheckIncorrect.json | 3 +- .../contradictionsCriteriaCheckCorrect.json | 3 +- .../contradictionsCriteriaCheckIncorrect.json | 3 +- .../checkers/deathTimeWindowCheckCorrect.json | 12 +-- .../deathTimeWindowCheckIncorrect.json | 12 +-- .../checkers/domainTypeCheckCorrect.json | 3 +- .../checkers/domainTypeCheckIncorrect.json | 3 +- .../checkers/drugDomainCheckCorrect.json | 9 +- .../checkers/drugDomainCheckIncorrect.json | 9 +- .../checkers/drugEraCheckCorrect.json | 9 +- .../duplicatesConceptSetCheckCorrect.json | 3 +- .../duplicatesConceptSetCheckIncorrect.json | 3 +- .../duplicatesCriteriaCheckCorrect.json | 3 +- .../duplicatesCriteriaCheckIncorrect.json | 3 +- .../checkers/emptyCensoringCriteriaList.json | 3 +- .../checkers/emptyCorrelatedCriteria.json | 3 +- .../emptyDemographicCheckCorrect.json | 9 +- .../emptyDemographicCheckIncorrect.json | 9 +- .../checkers/emptyInclusionRules.json | 3 +- .../checkers/emptyPrimaryCriteriaList.json | 3 +- .../eventsProgressionCheckCorrect.json | 9 +- .../eventsProgressionCheckIncorrect.json | 9 +- .../inclusionRulesCheckValueCorrect.json | 3 +- .../inclusionRulesCheckValueIncorrect.json | 3 +- .../checkers/noExitCriteriaCheck.json | 5 +- .../noExitCriteriaCheckEarliestEvent.json | 3 +- .../primaryCriteriaCheckValueCorrect.json | 3 +- .../primaryCriteriaCheckValueIncorrect.json | 3 +- .../checkers/timePatternCheckCorrect.json | 12 +-- .../checkers/timePatternCheckIncorrect.json | 21 ++-- .../resources/checkers/unusedConceptSet.json | 3 +- .../checkers/unusedConceptSetCorrect.json | 3 +- .../allCriteria/allCriteriaExpression.json | 95 +++++++------------ .../correlatedCriteria/countsExpression.json | 6 +- .../correlatedCriteria/groupExpression.json | 17 +++- .../correlatedCriteria/visitExpression.json | 7 +- .../eraDupes/eraDupesExpression.json | 2 + .../eraDupes/eraDupes_VERIFY.json | 14 +-- .../exits/censorEventExpression.json | 2 + .../continuousExposureCensorExpression.json | 7 +- .../exits/continuousExposureExpression.json | 7 +- .../exits/fixedOffsetCensorExpression.json | 2 + .../exits/fixedOffsetExpression.json | 2 + .../firstOccurrenceTestExpression.json | 1 + .../inclusionRules/simpleInclusionRule.json | 2 + .../limits/limitExpression.json | 5 +- .../limits/limitTest_VERIFY.json | 30 +----- .../printfriendly/allAttributes.json | 39 +++++--- .../resources/printfriendly/anyCondition.json | 1 + .../printfriendly/censorCriteria.json | 5 +- .../printfriendly/conceptSet_simple.json | 1 + .../resources/printfriendly/conditionEra.json | 15 ++- .../printfriendly/conditionOccurrence.json | 7 +- .../continuousObservation_none.json | 1 + .../continuousObservation_post.json | 1 + .../continuousObservation_prior.json | 1 + .../continuousObservation_priorpost.json | 1 + .../printfriendly/countCriteria.json | 24 +++-- .../printfriendly/countDistinctCriteria.json | 16 +++- .../printfriendly/customEraExit.json | 5 + .../resources/printfriendly/dateOffset.json | 2 + src/test/resources/printfriendly/death.json | 3 +- .../printfriendly/deviceExposure.json | 8 +- src/test/resources/printfriendly/doseEra.json | 26 +++-- src/test/resources/printfriendly/drugEra.json | 5 +- .../resources/printfriendly/drugExposure.json | 1 + .../resources/printfriendly/measurement.json | 9 +- .../printfriendly/noCensorCriteria.json | 1 + .../printfriendly/nullCodesetId.json | 1 + .../resources/printfriendly/observation.json | 2 + .../printfriendly/observationPeriod_1.json | 2 + .../printfriendly/procedureOccurrence.json | 4 +- .../resources/printfriendly/specimen.json | 4 +- src/test/resources/printfriendly/visit.json | 4 +- .../resources/printfriendly/visitDetail.json | 2 + 102 files changed, 547 insertions(+), 466 deletions(-) diff --git a/.editorconfig b/.editorconfig index 11a58414..d6d2a1b6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,4 @@ root = true [*.{java,sql}] charset = utf-8 indent_style = space -indent_size = 2 +indent_size = 4 diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 697b746e..8bffe26e 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -23,6 +23,7 @@ import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; + import org.apache.commons.lang3.builder.EqualsBuilder; import org.ohdsi.circe.cohortdefinition.*; import org.ohdsi.circe.vocabulary.Concept; @@ -105,16 +106,17 @@ public static Predicate compare(Concept source) { .append(concept.vocabularyId, source.vocabularyId) .build(); } + public static int compareTo(ObservationFilter filter, Window window) { - int range1 , range2Start , range2End; - if(Objects.nonNull(window.start) && Objects.nonNull(window.start.days)){ - range1 = filter.postDays + filter.priorDays; - range2Start = window.start.coeff * window.start.days; - range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; - }else { - range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; - range2Start = getTimeInSeconds(window.start); - range2End = getTimeInSeconds(window.end); + int range1, range2Start, range2End; + if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) && window.start.days != 0)) { + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + } else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); } return range1 - (range2End - range2Start); } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index 3d814885..e4294203 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -45,13 +45,7 @@ protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) .isA(DateOffsetStrategy.class) .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offset || 0 == dateOffsetStrategy.offsetUnitValue)) - .then(dateOffsetStrategy -> { - if(0 == dateOffsetStrategy.offset){ - reporter.add(String.format(DAYS_OFFSET_WARNING), "Days"); - }else { - reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)); - } - })); + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 3c6db7c9..89e437eb 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -42,12 +42,10 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni String name = CriteriaNameHelper.getCriteriaName(criteria.criteria) + " at " + groupName; Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) - .when(c -> c.startWindow != null && ((c.startWindow.start != null - && c.startWindow.start.days != null) || (c.startWindow.end != null - && c.startWindow.end.days != null)) - || ((c.startWindow.start.timeUnitValue != null) - && (c.startWindow.end != null) - && c.startWindow.end.timeUnitValue != null)) + .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) + || (c.startWindow.end != null && c.startWindow.end.days != null)) + || c.startWindow != null && (( c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) + || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java index 01d4cdbd..88606c5f 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheck.java @@ -53,26 +53,23 @@ protected void checkInclusionRules(final CohortExpression expression, WarningRep } private void checkWindow(Window window, WarningReporter reporter, String name) { - Optional.ofNullable(window) - .map(w -> checkEndpoint(w, name, "start")) - .ifPresent(reporter::add); + if (Objects.isNull(window)) { + return; + } + checkAndReportIfNegative(window.start, reporter, name, "start"); + checkAndReportIfNegative(window.end, reporter, name, "end"); - Optional.ofNullable(window) - .map(w -> checkEndpoint(w, name, "end")) - .ifPresent(reporter::add); } - private String checkEndpoint(Window window, String name, String endpointType) { - boolean hasValid = Objects.nonNull(window.start) && Objects.nonNull(window.start.days < 0 ? window.start.days : window.start.timeUnitValue) && window.start.days < 0 ? window.start.days < 0 : window.start.timeUnitValue < 0; - return Optional.of(window) - .filter(w -> hasValid) - .map(w -> String.format(NEGATIVE_VALUE_ERROR, name, getEndpointValue(w.start), endpointType)) - .orElse(null); - } - private Object getEndpointValue(Window.Endpoint endpoint) { - return Objects.nonNull(endpoint.days) ? endpoint.days : endpoint.timeUnitValue; + private void checkAndReportIfNegative(Window.Endpoint windowDetails, WarningReporter reporter, String name, String type) { + if (Objects.nonNull(windowDetails) && Objects.nonNull(windowDetails.days) && windowDetails.days < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, windowDetails.days, type); + } else if (Objects.nonNull(windowDetails) && Objects.nonNull(windowDetails.timeUnitValue) && windowDetails.timeUnitValue < 0) { + reporter.add(NEGATIVE_VALUE_ERROR, name, windowDetails.timeUnitValue, type); + } } + private void checkObservationFilter(ObservationFilter filter, WarningReporter reporter, String name) { if (Objects.nonNull(filter)) { if (filter.priorDays < 0) { diff --git a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java index 561bf9b7..1ff6c6f3 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/TimePatternCheck.java @@ -102,7 +102,7 @@ private Integer startDays(Window window) { .orElse(0)); }) .orElse(0); -} + } class TimeWindowInfo { private String name; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 690d3675..e22b520a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -352,9 +352,10 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ if(!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && expression.collapseSettings.eraPad != 0){ resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", "day"); }else{ + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); - resultSql = StringUtils.replace(resultSql, "@era_pad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -574,7 +575,7 @@ else if (startWindow.end.timeUnitValue != null) { clauses.add(String.format("%s <= %s", startEventDateExpression, endExpression)); } - // EndWindow + // EndWindow Window endWindow = criteria.endWindow; if (endWindow != null) { @@ -584,26 +585,26 @@ else if (startWindow.end.timeUnitValue != null) { if (endWindow.start.days != null) { startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); } else if (endWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); - } else { - startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; - } + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); + } else { + startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + } - if (startExpression != null) { - clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); - } + if (startExpression != null) { + clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); + } if (endWindow.end.days != null) { endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); } else if (endWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); - } else { - endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; - } + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); + } else { + endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + } - if (endExpression != null) { - clauses.add(String.format("%s <= %s", endEventDateExpression, endExpression)); - } + if (endExpression != null) { + clauses.add(String.format("%s <= %s", endEventDateExpression, endExpression)); + } } // RestrictVisit @@ -617,7 +618,7 @@ else if (startWindow.end.timeUnitValue != null) { return query; } - public String getWindowedCriteriaQuery(WindowedCriteria criteria, String eventTable) { + public String getWindowedCriteriaQuery(WindowedCriteria criteria, String eventTable) { String query = getWindowedCriteriaQuery(WINDOWED_CRITERIA_TEMPLATE, criteria, eventTable, null); return query; } @@ -775,13 +776,15 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - if(strat.offset != 0){ - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + if(strat.offset != 0){ + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); }else { strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); + return strategySql; } @@ -798,8 +801,18 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { } String strategySql = StringUtils.replace(CUSTOM_ERA_STRATEGY_TEMPLATE, "@eventTable", eventTable); strategySql = StringUtils.replace(strategySql, "@drugCodesetId", strat.drugCodesetId.toString()); - strategySql = StringUtils.replace(strategySql, "@gapDays", Integer.toString(strat.gapDays)); - strategySql = StringUtils.replace(strategySql, "@offset", Integer.toString(strat.offset)); + if (IntervalUnit.DAY.getName().equals(strat.gapUnit)) { + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); + } else { + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); + } + strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); + if (IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + } else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + } + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); strategySql = StringUtils.replace(strategySql, "@drugExposureEndDateExpression", drugExposureEndDateExpression); return strategySql; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 26170865..c9d44214 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -18,16 +18,18 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public class CollapseSettings { @JsonProperty("CollapseType") public CollapseType collapseType = CollapseType.ERA; @JsonProperty("EraPad") - public int eraPad = 0; + public int eraPad = 0; @JsonProperty("EraPadUnit") - public String eraPadUnit; + public String eraPadUnit = "day"; @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java index f585e73d..aa307330 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java @@ -31,9 +31,17 @@ public class CustomEraStrategy extends EndStrategy { @JsonProperty("GapDays") public int gapDays = 0; + @JsonProperty("GapUnit") + public String gapUnit = "day"; + @JsonProperty("GapUnitValue") + public int gapUnitValue = 0; @JsonProperty("Offset") public int offset = 0; + @JsonProperty("OffsetUnit") + public String offsetUnit = "day"; + @JsonProperty("OffsetUnitValue") + public int offsetUnitValue = 0; @JsonProperty("DaysSupplyOverride") public Integer daysSupplyOverride = null; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index f2d0ed6b..7e5e27ba 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -18,12 +18,14 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author Chris Knoll */ +@JsonIgnoreProperties(ignoreUnknown = true) public class DateOffsetStrategy extends EndStrategy { public enum DateField { @@ -38,7 +40,7 @@ public enum DateField { @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; @JsonProperty("OffsetUnit") - public String offsetUnit; + public String offsetUnit = "day"; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java index 2a40b9cb..e55b58e7 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -3,6 +3,7 @@ import jdk.nashorn.internal.objects.annotations.Getter; public enum IntervalUnit { + DAY("day"), HOUR("hour"), MINUTE("minute"), SECOND("second"); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index e746eb50..22be0794 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -18,19 +18,25 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author cknoll1 */ +@JsonIgnoreProperties(ignoreUnknown = true) public class Window { public static class Endpoint { + + @JsonProperty("Days") + public Integer days; @JsonProperty("TimeUnitValue") public Integer timeUnitValue; @JsonProperty("TimeUnit") - public String timeUnit = IntervalUnit.DAY.getName(); + public String timeUnit="day"; + @JsonProperty("Coeff") public int coeff; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 8ca6c2a5..1a7b1b61 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -94,7 +94,8 @@ protected List resolveSelectClauses(T criteria) { } // dateAdjustment or default start/end dates if (criteria.dateAdjustment != null) { - selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, + selectCols.add( + BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))")); } else { diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index 8f2cff2e..f1ec4503 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -292,46 +292,14 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi <#macro CountCriteria countCriteria level=0 indexLabel="cohort entry">having <#if countCriteria.occurrence.type == 0 && countCriteria.occurrence.count == 0>no<#else>${inputTypes.getCountType(countCriteria)} ${countCriteria.occurrence.count}<#-- --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> -<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]> - <#if countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??> - <#local temp> - <@inputTypes.Window countCriteria.startWindow indexLabel /> - - <#local windowParts+=[temp]> - - <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.days?? || countCriteria.endWindow.end.days??)> -<#local temp> - <@inputTypes.Window countCriteria.endWindow indexLabel /> - - <#local windowParts+=[temp]> - - <#if countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??> - <#local temp> - <@inputTypes.Window countCriteria.startWindow indexLabel /> - - <#local windowParts+=[temp]> - - <#if countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> - <#local temp> - <@inputTypes.Window countCriteria.endWindow indexLabel /> - - <#local windowParts+=[temp]> - -<#if countCriteria.restrictVisit!false> - <#local temp>at same visit as ${indexLabel} - - <#local restrictParts+=[temp]> - -<#if countCriteria.ignoreObservationPeriod!false> - <#local temp>allow events outside observation period - <#local restrictParts+=[temp]> - - <#if windowParts?size gt 0>${windowParts?join(" and ")} - <#if restrictParts?size gt 0>; - - <#if restrictParts?size gt 0>${restrictParts?join(" and ")} - - +<#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> + <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> + <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> + <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if +windowParts?size gt 0>${windowParts?join(" and ")}<#if restrictParts?size gt 0>; <#if +restrictParts?size gt 0>${restrictParts?join(" and ")} <#-- Demographic Criteria --> <#macro DemographicCriteria c level=0 indexLabel = "cohort entry"><#local attrs = []><#local temp><@AgeGenderCriteria ageAtStart=c.age!{} gender=c.gender!{} /><#if temp?has_content><#local attrs+=[temp]><#local diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 15e16a34..1fad6ffb 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -24,12 +24,7 @@ The person exits the cohort at the end of continuous observation. <#assign dateOffsetFieldOptions = [{"id": "StartDate", "name": "start date"}, {"id": "EndDate", "name": "end date"}]> <#macro DateOffsetStrategy s> - <#if s.offset?? && s.offset == "day" > - The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offset "day"/>. - <#else > - The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. - - + The cohort end date will be offset from index event's ${utils.optionName(dateOffsetFieldOptions, s.dateField)} plus <@utils.formatValue s.offsetUnitValue s.offsetUnit/>. <#macro CustomEraStrategy s> diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 45c59c00..3c2fe52a 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -11,13 +11,13 @@ END Note!!!! <#import "./utils.ftl" as utils> <#-- DateRange --> <#assign dateRangeOptions = [ - {"id": "lt", "name": "before"}, - {"id": "lte", "name": "on or Before"}, - {"id": "eq", "name": "on"}, - {"id": "gt", "name": "after"}, - {"id": "gte", "name": "on or after"}, - {"id": "bt","name": "between"}, - {"id": "!bt", "name": "not between"} +{"id": "lt", "name": "before"}, +{"id": "lte", "name": "on or Before"}, +{"id": "eq", "name": "on"}, +{"id": "gt", "name": "after"}, +{"id": "gte", "name": "on or after"}, +{"id": "bt","name": "between"}, +{"id": "!bt", "name": "not between"} ]> <#macro DateRange range>${utils.optionName(dateRangeOptions, range.op)} <#-- @@ -25,64 +25,64 @@ END Note!!!! --><#if range.op?ends_with("bt")> and <#if range.extent?has_content>${utils.formatDate(range.extent)}<#else>_empty_ <#-- ConceptList --> -<#macro ConceptList list quote="\""><#if (list?size > 0)><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if -item?counter gt 1><#if item?counter == list?size> or <#else>, ${item}<#else>[none specified] +<#macro ConceptList list quote="\""><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if item?counter gt 1><#if +item?counter == list?size> or <#else>, ${item} <#-- ConceptSetSelection --> <#macro ConceptSetSelection selection defaultName="any"><#if selection.isExcluded!false>not in ${utils.codesetName(selection.codesetId!"", defaultName)} <#-- NumericRange --> <#assign numericRangeOptions = [ - {"id": "lt", "name": "<"}, - {"id": "lte", "name": "<="}, - {"id": "eq", "name": "="}, - {"id": "gt", "name": ">"}, - {"id": "gte", "name": ">="}, - {"id": "bt", "name": "between"}, - {"id": "!bt", "name": "not Between"} +{"id": "lt", "name": "<"}, +{"id": "lte", "name": "<="}, +{"id": "eq", "name": "="}, +{"id": "gt", "name": ">"}, +{"id": "gte", "name": ">="}, +{"id": "bt", "name": "between"}, +{"id": "!bt", "name": "not Between"} ]> <#macro NumericRange range>${utils.optionName(numericRangeOptions, range.op)} ${range.value!""}<#if range.op?ends_with("bt")> and ${range.extent!""} <#-- TextFilter --> <#assign textFilterOptions = [ - {"id": 'startsWith', "name": 'starting with'}, - {"id": 'contains', "name": 'containing'}, - {"id": 'endsWith', "name": 'ending with'}, - {"id": '!startsWith', "name": 'not starting with'}, - {"id": '!contains',"name": 'not containing'}, - {"id": '!endsWith',"name": 'not ending with'} +{"id": 'startsWith', "name": 'starting with'}, +{"id": 'contains', "name": 'containing'}, +{"id": 'endsWith', "name": 'ending with'}, +{"id": '!startsWith', "name": 'not starting with'}, +{"id": '!contains',"name": 'not containing'}, +{"id": '!endsWith',"name": 'not ending with'} ]/> <#macro TextFilter filter>${utils.optionName(textFilterOptions, filter.op)} "${filter.text!""}" <#-- Limits --> <#assign resultLimitOptions = [ - {"id": "All", "name": "all events"}, - {"id": "First", "name": "earliest event"}, - {"id": "Last", "name": "latest event"} +{"id": "All", "name": "all events"}, +{"id": "First", "name": "earliest event"}, +{"id": "Last", "name": "latest event"} ]/> <#macro Limit limit>${utils.optionName(resultLimitOptions, limit.type)} <#-- Group --> <#assign groupTypeOptions = [ - {"id": 'ALL', "name": 'all'}, - {"id": 'ANY', "name": 'any'}, - {"id": 'AT_LEAST', "name": 'at least'}, - {"id": 'AT_MOST', "name": 'at most'} +{"id": 'ALL', "name": 'all'}, +{"id": 'ANY', "name": 'any'}, +{"id": 'AT_LEAST', "name": 'at least'}, +{"id": 'AT_MOST', "name": 'at most'} ]> <#-- Count (with Window) --> <#assign countTypeOptions = [ - {"id": 1, "name": 'at most'}, - {"id": 0, "name": 'exactly'}, - {"id": 2, "name": 'at least'} +{"id": 1, "name": 'at most'}, +{"id": 0, "name": 'exactly'}, +{"id": 2, "name": 'at least'} ]> <#function getCountType(countCriteria)> - <#return utils.optionName(countTypeOptions, countCriteria.occurrence.type)> + <#return utils.optionName(countTypeOptions, countCriteria.occurrence.type)> <#function whichEventPart useEnd><#if useEnd><#return "ending"><#else><#return "starting"> diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index c606ec0f..f70ad927 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -26,7 +26,7 @@ INTO #strategy_ends from @eventTable et JOIN ( - select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(day,@offset, ENDS.era_end_date) as era_end_date + select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(@offsetUnit, @offsetUnitValue, ENDS.era_end_date) as era_end_date from ( select de.person_id, de.drug_exposure_start_date, MIN(e.END_DATE) as era_end_date @@ -34,7 +34,7 @@ JOIN JOIN ( --cteEndDates - select PERSON_ID, DATEADD(day,-1 * @gapDays,EVENT_DATE) as END_DATE -- unpad the end date by @gapDays + select PERSON_ID, DATEADD(@gapUnit,-1 * @gapUnitValue,EVENT_DATE) as END_DATE -- unpad the end date by @gapDays FROM ( select PERSON_ID, EVENT_DATE, EVENT_TYPE, @@ -49,7 +49,7 @@ JOIN UNION ALL -- add the end dates with NULL as the row number, padding the end dates by @gapDays to allow a grace period for overlapping ranges. - select PERSON_ID, DATEADD(day,@gapDays,DRUG_EXPOSURE_END_DATE), 1 as EVENT_TYPE, NULL + select PERSON_ID, DATEADD(@gapUnit,@gapUnitValue,DRUG_EXPOSURE_END_DATE), 1 as EVENT_TYPE, NULL FROM #drugTarget D ) RAWDATA ) E diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index cac7c35f..b79289dd 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,20 +1,9 @@ -- date offset strategy -SELECT - event_id, - person_id, - CASE - WHEN @offset IS NOT NULL - THEN - CASE - WHEN DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date THEN op_end_date - ELSE DATEADD(@offsetUnit, @offsetUnitValue, @dateField) - END - ELSE - CASE - WHEN DATEADD(day, @offset, @dateField) > op_end_date THEN op_end_date - ELSE DATEADD(day, @offset, @dateField) - END - END AS end_date +select event_id, + person_id, + case + when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date + else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date INTO #strategy_ends -FROM @eventTable; +from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 8fad4578..b2d1145b 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -63,13 +63,7 @@ from ( --cteEnds JOIN ( -- cteEndDates SELECT person_id, - CASE - WHEN - @eraconstructorpad IS NOT NULL - THEN - DATEADD(day, -1 * @eraconstructorpad, event_date) - ELSE - DATEADD(@era_pad_unit, -1 * @era_pad, event_date) END AS end_date + DATEADD(@era_pad_unit, -1 * @eraconstructorpad, event_date) as end_date FROM ( SELECT @@ -87,10 +81,9 @@ from ( --cteEnds UNION ALL - SELECT person_id - , DATEADD(@era_pad_unit, @era_pad, end_date) as end_date + , DATEADD(@era_pad_unit, -1 * @eraconstructorpad, end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 2f4fc4c5..0bafd06c 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -113,23 +113,94 @@ public void compareTo() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.timeUnitValue = 10; - window.start.timeUnit = IntervalUnit.DAY.getName(); + window.start.days = 10; + window.start.timeUnitValue = null; + window.start.timeUnit = null; window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.timeUnitValue = 5; - window.end.timeUnit = IntervalUnit.DAY.getName(); + window.end.days = 5; + window.end.timeUnitValue = null; + window.end.timeUnit = null; window.end.coeff = 1; assertEquals(0, Comparisons.compareTo(filter, window)); - window.start.timeUnitValue = 5; - window.start.timeUnit = IntervalUnit.DAY.getName(); - window.end.timeUnitValue = 5; - window.end.timeUnit = IntervalUnit.DAY.getName(); - assertEquals(432000, Comparisons.compareTo(filter, window)); + window.start.days = 5; + window.end.days = 5; + + window.start.timeUnitValue = null; + window.start.timeUnit = null; + window.end.timeUnitValue = null; + window.end.timeUnit = null; + + assertEquals(5, Comparisons.compareTo(filter, window)); + } + + @Test + public void compareToHours() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.days = 0; + window.start.timeUnit = "hour"; + window.start.timeUnitValue = 72; + window.start.coeff = -1; + + window.end.days = 0; + window.end.timeUnit = "hour"; + window.end.timeUnitValue = 120; + window.end.coeff = 1; + + assertEquals(604800, Comparisons.compareTo(filter, window)); + } + @Test + public void compareToMinute() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.days = 0; + window.start.timeUnit = "minute"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.days = 0; + window.end.timeUnit = "minute"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + + assertEquals(1289700, Comparisons.compareTo(filter, window)); + } + + @Test + public void compareToSecond() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.days = 0; + window.start.timeUnit = "second"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.days = 0; + window.end.timeUnit = "second"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + + assertEquals(1295895, Comparisons.compareTo(filter, window)); } @Test @@ -243,10 +314,12 @@ public void compareCriteria() { public void isBefore() { Window window = new Window(); window.start = new Window.Endpoint(); - window.start.timeUnitValue = 1; + window.start.days = 1; + window.start.timeUnitValue = null; window.start.coeff = -1; window.end = new Window.Endpoint(); - window.end.timeUnitValue = 1; + window.end.days = 1; + window.end.timeUnitValue = null; window.end.coeff = 1; assertEquals(false, Comparisons.isBefore(window)); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index c20ca187..83f14077 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -29,7 +29,8 @@ public static Window getPrior365Window() { // index starts between 365d before Window.Endpoint startPoint = new Window.Endpoint(); startPoint.coeff = -1; - startPoint.timeUnitValue = 365; + startPoint.days = 365; + startPoint.timeUnitValue = null; prior365Window.start = startPoint; // ... and 0 days before Window.Endpoint endPoint = new Window.Endpoint(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index 4e0dc1a0..47c41559 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -625,21 +625,21 @@ public void nullCodesetIdTest() { public void nullCohortTest() { exceptionRule.expect(RuntimeException.class); pf.renderCohort((CohortExpression)null); - + } @Test public void nullConceptSetTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSet((ConceptSet)null); - + } @Test public void nullConceptSetListTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSetList((ConceptSet[])null); - + } @Test diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json index 13b41373..3e1ea072 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrect.json @@ -539,8 +539,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 1 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json index be6f11f6..266cc3e9 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json @@ -309,8 +309,9 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPadUnit": "hour", + "EraPadUnitValue": 0, + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueCorrect.json b/src/test/resources/checkers/censoringEventCheckValueCorrect.json index 8b517a9e..7df72672 100644 --- a/src/test/resources/checkers/censoringEventCheckValueCorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueCorrect.json @@ -408,8 +408,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json index e791905b..515aca1b 100644 --- a/src/test/resources/checkers/censoringEventCheckValueIncorrect.json +++ b/src/test/resources/checkers/censoringEventCheckValueIncorrect.json @@ -288,8 +288,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json index 9c9c05e3..34afceb4 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json @@ -115,8 +115,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json index af967543..e0d400d5 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json @@ -89,8 +89,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json index e64e1c55..7f224759 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckCorrect.json @@ -116,8 +116,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json index 92b71212..3153dd0c 100644 --- a/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/contradictionsCriteriaCheckIncorrect.json @@ -116,8 +116,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json index f51ad3d1..16f05ebc 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckCorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrect.json @@ -39,8 +39,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -84,8 +83,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -106,8 +104,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -134,8 +131,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json index 2b593bac..ad419909 100644 --- a/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json +++ b/src/test/resources/checkers/deathTimeWindowCheckIncorrect.json @@ -39,8 +39,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -84,8 +83,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -106,8 +104,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -134,8 +131,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckCorrect.json b/src/test/resources/checkers/domainTypeCheckCorrect.json index 678b7562..dc401077 100644 --- a/src/test/resources/checkers/domainTypeCheckCorrect.json +++ b/src/test/resources/checkers/domainTypeCheckCorrect.json @@ -217,8 +217,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/domainTypeCheckIncorrect.json b/src/test/resources/checkers/domainTypeCheckIncorrect.json index 9c9c05e3..34afceb4 100644 --- a/src/test/resources/checkers/domainTypeCheckIncorrect.json +++ b/src/test/resources/checkers/domainTypeCheckIncorrect.json @@ -115,8 +115,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckCorrect.json b/src/test/resources/checkers/drugDomainCheckCorrect.json index 0a070252..44f2dfae 100644 --- a/src/test/resources/checkers/drugDomainCheckCorrect.json +++ b/src/test/resources/checkers/drugDomainCheckCorrect.json @@ -162,14 +162,17 @@ "CustomEra": { "DrugCodesetId": 2, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugDomainCheckIncorrect.json b/src/test/resources/checkers/drugDomainCheckIncorrect.json index 64a99002..65259d56 100644 --- a/src/test/resources/checkers/drugDomainCheckIncorrect.json +++ b/src/test/resources/checkers/drugDomainCheckIncorrect.json @@ -162,14 +162,17 @@ "CustomEra": { "DrugCodesetId": 1, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/drugEraCheckCorrect.json b/src/test/resources/checkers/drugEraCheckCorrect.json index 565aad14..047e2f50 100644 --- a/src/test/resources/checkers/drugEraCheckCorrect.json +++ b/src/test/resources/checkers/drugEraCheckCorrect.json @@ -53,13 +53,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 0, - "TimeUnit": "day", + "Days": 0, "Coeff": -1 }, "End": { - "TimeUnitValue": 0, - "TimeUnit": "day", + "Days": 0, "Coeff": -1 }, "UseEventEnd": true @@ -78,8 +76,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json index 4b4cdbe3..4ad7d1c3 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckCorrect.json @@ -50,8 +50,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json index 1741beb8..da030ecf 100644 --- a/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesConceptSetCheckIncorrect.json @@ -73,8 +73,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json index b2d56fae..6c79f167 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json @@ -185,8 +185,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json index 05b5b93e..e726c78c 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json @@ -532,8 +532,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/emptyCensoringCriteriaList.json b/src/test/resources/checkers/emptyCensoringCriteriaList.json index 6dec6bdc..ff98150d 100644 --- a/src/test/resources/checkers/emptyCensoringCriteriaList.json +++ b/src/test/resources/checkers/emptyCensoringCriteriaList.json @@ -49,8 +49,7 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyCorrelatedCriteria.json b/src/test/resources/checkers/emptyCorrelatedCriteria.json index dbe91807..2b70c327 100644 --- a/src/test/resources/checkers/emptyCorrelatedCriteria.json +++ b/src/test/resources/checkers/emptyCorrelatedCriteria.json @@ -148,8 +148,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckCorrect.json b/src/test/resources/checkers/emptyDemographicCheckCorrect.json index 34a612c0..8d3cc58c 100644 --- a/src/test/resources/checkers/emptyDemographicCheckCorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckCorrect.json @@ -456,14 +456,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 30, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 30, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json index 9ac8d92f..58428c18 100644 --- a/src/test/resources/checkers/emptyDemographicCheckIncorrect.json +++ b/src/test/resources/checkers/emptyDemographicCheckIncorrect.json @@ -452,14 +452,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 30, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 30, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/emptyInclusionRules.json b/src/test/resources/checkers/emptyInclusionRules.json index e0af1736..d7def9e6 100644 --- a/src/test/resources/checkers/emptyInclusionRules.json +++ b/src/test/resources/checkers/emptyInclusionRules.json @@ -54,8 +54,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/emptyPrimaryCriteriaList.json b/src/test/resources/checkers/emptyPrimaryCriteriaList.json index acbd8ac3..c4c7425d 100644 --- a/src/test/resources/checkers/emptyPrimaryCriteriaList.json +++ b/src/test/resources/checkers/emptyPrimaryCriteriaList.json @@ -39,8 +39,7 @@ "InclusionRules": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckCorrect.json b/src/test/resources/checkers/eventsProgressionCheckCorrect.json index 0cf117c6..5ebed29c 100644 --- a/src/test/resources/checkers/eventsProgressionCheckCorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckCorrect.json @@ -76,14 +76,17 @@ "CustomEra": { "DrugCodesetId": 2, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json index 5d927fb3..8cdbb5a2 100644 --- a/src/test/resources/checkers/eventsProgressionCheckIncorrect.json +++ b/src/test/resources/checkers/eventsProgressionCheckIncorrect.json @@ -76,14 +76,17 @@ "CustomEra": { "DrugCodesetId": 2, "GapDays": 0, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 0, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json index 270f534d..121726e5 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueCorrect.json @@ -478,8 +478,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json index afdc0ef0..50a9a34f 100644 --- a/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json +++ b/src/test/resources/checkers/inclusionRulesCheckValueIncorrect.json @@ -268,8 +268,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/noExitCriteriaCheck.json b/src/test/resources/checkers/noExitCriteriaCheck.json index 822578ab..5cf211ff 100644 --- a/src/test/resources/checkers/noExitCriteriaCheck.json +++ b/src/test/resources/checkers/noExitCriteriaCheck.json @@ -77,9 +77,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" -} +} \ No newline at end of file diff --git a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json index 913dc418..3b63bd3b 100644 --- a/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json +++ b/src/test/resources/checkers/noExitCriteriaCheckEarliestEvent.json @@ -77,8 +77,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json index 79152e6b..145d690a 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json @@ -1358,8 +1358,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json index e1a3dd8b..24864bf0 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json @@ -701,8 +701,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/timePatternCheckCorrect.json b/src/test/resources/checkers/timePatternCheckCorrect.json index 5b80ee70..75146327 100644 --- a/src/test/resources/checkers/timePatternCheckCorrect.json +++ b/src/test/resources/checkers/timePatternCheckCorrect.json @@ -59,8 +59,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -81,8 +80,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -103,8 +101,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -126,8 +123,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/timePatternCheckIncorrect.json b/src/test/resources/checkers/timePatternCheckIncorrect.json index 1488cbe4..964b92ad 100644 --- a/src/test/resources/checkers/timePatternCheckIncorrect.json +++ b/src/test/resources/checkers/timePatternCheckIncorrect.json @@ -88,8 +88,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "End": { @@ -110,8 +109,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "End": { @@ -132,8 +130,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 7, - "TimeUnit": "day", + "Days": 7, "Coeff": -1 }, "End": { @@ -155,8 +152,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -177,8 +173,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -199,8 +194,7 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { @@ -222,8 +216,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" diff --git a/src/test/resources/checkers/unusedConceptSet.json b/src/test/resources/checkers/unusedConceptSet.json index b37b5ee4..3cc898be 100644 --- a/src/test/resources/checkers/unusedConceptSet.json +++ b/src/test/resources/checkers/unusedConceptSet.json @@ -4433,8 +4433,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/unusedConceptSetCorrect.json b/src/test/resources/checkers/unusedConceptSetCorrect.json index 78dae501..38143407 100644 --- a/src/test/resources/checkers/unusedConceptSetCorrect.json +++ b/src/test/resources/checkers/unusedConceptSetCorrect.json @@ -4826,8 +4826,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json index 35d19f07..a6cf2e0e 100644 --- a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json +++ b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpression.json @@ -287,13 +287,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -390,13 +388,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -512,13 +508,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -690,13 +684,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -803,13 +795,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1048,13 +1038,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1271,13 +1259,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1477,13 +1463,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1587,13 +1571,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1767,13 +1749,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -1950,13 +1930,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -2114,13 +2092,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -2149,13 +2125,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 100, - "TimeUnit": "day", + "Days": 100, "Coeff": -1 }, "End": { - "TimeUnitValue": 100, - "TimeUnit": "day", + "Days": 100, "Coeff": 1 }, "UseEventEnd": false @@ -2179,13 +2153,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 60, - "TimeUnit": "day", + "Days": 60, "Coeff": -1 }, "End": { - "TimeUnitValue": 60, - "TimeUnit": "day", + "Days": 60, "Coeff": 1 }, "UseEventEnd": false @@ -2203,13 +2175,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": -1 }, "End": { - "TimeUnitValue": 30, - "TimeUnit": "day", + "Days": 30, "Coeff": 1 }, "UseEventEnd": false @@ -2230,9 +2200,8 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "minute", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" -} +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json index 352fd00d..cd76dbba 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/countsExpression.json @@ -30,7 +30,7 @@ "ConditionOccurrence": { "CodesetId": 0 } - } + } ], "ObservationWindow": { "PriorDays": 0, @@ -54,8 +54,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -79,6 +80,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json index e2a4ee39..1b6f524d 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpression.json @@ -51,13 +51,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -75,13 +77,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -105,11 +109,13 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -129,11 +135,13 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -161,6 +169,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json index b31aa4e6..99e17ac8 100644 --- a/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json +++ b/src/test/resources/cohortgeneration/correlatedCriteria/visitExpression.json @@ -52,16 +52,18 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -88,6 +90,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 8b983658..9c3b8584 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -23,6 +23,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 0, "OffsetUnit": "hour", "OffsetUnitValue": 0 } @@ -30,6 +31,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 30, "EraPadUnit": "minute", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json index 4d640023..20615fa7 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json @@ -4,19 +4,7 @@ "cohort_definition_id":1, "subject_id":1, "cohort_start_date":"2009-06-16", - "cohort_end_date":"2009-06-19" - }, - { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2009-07-01", - "cohort_end_date":"2009-07-04" - }, - { - "cohort_definition_id":1, - "subject_id":1, - "cohort_start_date":"2009-07-27", - "cohort_end_date":"2009-07-30" + "cohort_end_date":"2009-06-27" } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/censorEventExpression.json b/src/test/resources/cohortgeneration/exits/censorEventExpression.json index 725f718c..72b8d5cc 100644 --- a/src/test/resources/cohortgeneration/exits/censorEventExpression.json +++ b/src/test/resources/cohortgeneration/exits/censorEventExpression.json @@ -50,6 +50,7 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", + "Offset": 31, "OffsetUnit": "day", "OffsetUnitValue": 31 } @@ -61,6 +62,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json index 09efd2fe..2fb0db21 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json @@ -51,12 +51,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 60, - "Offset": 9999 + "GapUnit": "day", + "GapUnitValue": 60, + "Offset": 9999, + "OffsetUnit": "day", + "OffsetUnitValue": 9999 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index b142ef89..bd12cbe7 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -51,12 +51,17 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 60, - "Offset": 0 + "GapUnit": "day", + "GapUnitValue": 60, + "Offset": 0, + "OffsetUnit": "day", + "OffsetUnitValue": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json index 7faca602..542a8829 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetCensorExpression.json @@ -50,6 +50,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 99999, "OffsetUnit": "day", "OffsetUnitValue": 99999 } @@ -57,6 +58,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json index 0f5ab2e2..74fa1daf 100644 --- a/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json +++ b/src/test/resources/cohortgeneration/exits/fixedOffsetExpression.json @@ -50,6 +50,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 31, "OffsetUnit": "day", "OffsetUnitValue": 31 } @@ -57,6 +58,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json index e66c806f..0713b0dc 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json @@ -44,6 +44,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json index 200fe127..3580bf44 100644 --- a/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json +++ b/src/test/resources/cohortgeneration/inclusionRules/simpleInclusionRule.json @@ -86,6 +86,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -106,6 +107,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/limits/limitExpression.json b/src/test/resources/cohortgeneration/limits/limitExpression.json index 23ae40b8..b638424b 100644 --- a/src/test/resources/cohortgeneration/limits/limitExpression.json +++ b/src/test/resources/cohortgeneration/limits/limitExpression.json @@ -54,8 +54,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -79,6 +80,7 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", + "Offset": 1, "OffsetUnit": "day", "OffsetUnitValue": 1 } @@ -86,6 +88,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 60, "EraPadUnit": "day", "EraPadUnitValue": 60 }, diff --git a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json index eb27b963..4f3fb3b6 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json @@ -4,37 +4,13 @@ "cohort_definition_id": 1, "subject_id": 1, "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-02-02" + "cohort_end_date": "2000-06-02" }, { "cohort_definition_id": 1, "subject_id": 1, - "cohort_start_date": "2000-06-01", - "cohort_end_date": "2000-08-02" - }, - { - "cohort_definition_id": 1, - "subject_id": 1, - "cohort_start_date": "2000-11-01", - "cohort_end_date": "2000-12-02" - }, - { - "cohort_definition_id": 2, - "subject_id": 1, - "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-02-02" - }, - { - "cohort_definition_id": 3, - "subject_id": 1, - "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-02-02" - }, - { - "cohort_definition_id": 5, - "subject_id": 1, - "cohort_start_date": "2000-12-01", - "cohort_end_date": "2000-12-02" + "cohort_start_date": "2000-07-01", + "cohort_end_date": "2000-10-02" } ] } \ No newline at end of file diff --git a/src/test/resources/printfriendly/allAttributes.json b/src/test/resources/printfriendly/allAttributes.json index 1c5573d4..97a4241f 100644 --- a/src/test/resources/printfriendly/allAttributes.json +++ b/src/test/resources/printfriendly/allAttributes.json @@ -70,21 +70,24 @@ }, "StartWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false }, "EndWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -144,8 +147,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -177,8 +181,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -323,8 +328,9 @@ "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -357,8 +363,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -380,8 +387,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -462,8 +470,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -552,8 +561,9 @@ "Coeff": -1 }, "End": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -575,8 +585,9 @@ "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -683,11 +694,13 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -724,6 +737,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -810,7 +824,7 @@ }, "End": { "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -831,6 +845,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/anyCondition.json b/src/test/resources/printfriendly/anyCondition.json index 73ef7919..89ed96ca 100644 --- a/src/test/resources/printfriendly/anyCondition.json +++ b/src/test/resources/printfriendly/anyCondition.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/censorCriteria.json b/src/test/resources/printfriendly/censorCriteria.json index e2948c49..8f69c475 100644 --- a/src/test/resources/printfriendly/censorCriteria.json +++ b/src/test/resources/printfriendly/censorCriteria.json @@ -324,13 +324,15 @@ }, "StartWindow": { "Start": { + "Days": 365, "TimeUnitValue": 365, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 60, "TimeUnitValue": 60, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -358,6 +360,7 @@ ], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/conceptSet_simple.json b/src/test/resources/printfriendly/conceptSet_simple.json index de95aee1..09cd48cf 100644 --- a/src/test/resources/printfriendly/conceptSet_simple.json +++ b/src/test/resources/printfriendly/conceptSet_simple.json @@ -74,6 +74,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/conditionEra.json b/src/test/resources/printfriendly/conditionEra.json index 9c7865f5..8ba4378d 100644 --- a/src/test/resources/printfriendly/conditionEra.json +++ b/src/test/resources/printfriendly/conditionEra.json @@ -37,26 +37,30 @@ }, "StartWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false }, "EndWindow": { "Start": { + "Days": 7, "TimeUnitValue": 7, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { + "Days": 90, "TimeUnitValue": 90, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": true @@ -140,11 +144,13 @@ }, "StartWindow": { "Start": { + "Days": 90, "TimeUnitValue": 90, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -165,6 +171,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/conditionOccurrence.json b/src/test/resources/printfriendly/conditionOccurrence.json index 7cd95ca8..97f57cc9 100644 --- a/src/test/resources/printfriendly/conditionOccurrence.json +++ b/src/test/resources/printfriendly/conditionOccurrence.json @@ -52,8 +52,9 @@ }, "StartWindow": { "Start": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "End": { @@ -205,8 +206,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": true @@ -244,6 +246,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": 1 diff --git a/src/test/resources/printfriendly/continuousObservation_none.json b/src/test/resources/printfriendly/continuousObservation_none.json index 9d63f1b4..354fd870 100644 --- a/src/test/resources/printfriendly/continuousObservation_none.json +++ b/src/test/resources/printfriendly/continuousObservation_none.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/continuousObservation_post.json b/src/test/resources/printfriendly/continuousObservation_post.json index 05538e32..9587c373 100644 --- a/src/test/resources/printfriendly/continuousObservation_post.json +++ b/src/test/resources/printfriendly/continuousObservation_post.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/continuousObservation_prior.json b/src/test/resources/printfriendly/continuousObservation_prior.json index 774f4a73..dc43eebe 100644 --- a/src/test/resources/printfriendly/continuousObservation_prior.json +++ b/src/test/resources/printfriendly/continuousObservation_prior.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/continuousObservation_priorpost.json b/src/test/resources/printfriendly/continuousObservation_priorpost.json index 56abf230..6cf78eaa 100644 --- a/src/test/resources/printfriendly/continuousObservation_priorpost.json +++ b/src/test/resources/printfriendly/continuousObservation_priorpost.json @@ -24,6 +24,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/countCriteria.json b/src/test/resources/printfriendly/countCriteria.json index d5561bd3..700ea4b8 100644 --- a/src/test/resources/printfriendly/countCriteria.json +++ b/src/test/resources/printfriendly/countCriteria.json @@ -30,13 +30,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -78,8 +80,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -100,8 +103,9 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -240,6 +244,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -272,6 +277,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -303,6 +309,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -337,6 +344,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -356,6 +364,7 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -384,13 +393,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -411,6 +422,7 @@ "Coeff": -1 }, "End": { + "Days": 31, "TimeUnitValue": 31, "TimeUnit": "day", "Coeff": -1 diff --git a/src/test/resources/printfriendly/countDistinctCriteria.json b/src/test/resources/printfriendly/countDistinctCriteria.json index 45bed9c7..f5dbe6d8 100644 --- a/src/test/resources/printfriendly/countDistinctCriteria.json +++ b/src/test/resources/printfriendly/countDistinctCriteria.json @@ -30,13 +30,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -79,8 +81,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -107,8 +110,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -131,8 +135,9 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -173,6 +178,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/customEraExit.json b/src/test/resources/printfriendly/customEraExit.json index e74f2963..fe2dca24 100644 --- a/src/test/resources/printfriendly/customEraExit.json +++ b/src/test/resources/printfriendly/customEraExit.json @@ -43,13 +43,18 @@ "CustomEra": { "DrugCodesetId": 0, "GapDays": 14, + "GapUnit": "day", + "GapUnitValue": 14, "Offset": 1, + "OffsetUnit": "day", + "OffsetUnitValue": 1, "DaysSupplyOverride": 7 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/dateOffset.json b/src/test/resources/printfriendly/dateOffset.json index f52faaeb..e6a65744 100644 --- a/src/test/resources/printfriendly/dateOffset.json +++ b/src/test/resources/printfriendly/dateOffset.json @@ -42,6 +42,7 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", + "Offset": 7, "OffsetUnit": "day", "OffsetUnitValue": 7 } @@ -49,6 +50,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/death.json b/src/test/resources/printfriendly/death.json index 4c232614..43c585a1 100644 --- a/src/test/resources/printfriendly/death.json +++ b/src/test/resources/printfriendly/death.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false diff --git a/src/test/resources/printfriendly/deviceExposure.json b/src/test/resources/printfriendly/deviceExposure.json index 3f6a4e10..96f9f895 100644 --- a/src/test/resources/printfriendly/deviceExposure.json +++ b/src/test/resources/printfriendly/deviceExposure.json @@ -66,8 +66,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -181,8 +182,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -217,11 +219,13 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 30, "TimeUnitValue": 30, "TimeUnit": "day", "Coeff": 1 diff --git a/src/test/resources/printfriendly/doseEra.json b/src/test/resources/printfriendly/doseEra.json index 1c1493d4..7fb29479 100644 --- a/src/test/resources/printfriendly/doseEra.json +++ b/src/test/resources/printfriendly/doseEra.json @@ -38,13 +38,15 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -62,13 +64,14 @@ }, "StartWindow": { "Start": { + "Days": 30, "TimeUnitValue": 30, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -172,13 +175,15 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -196,13 +201,15 @@ }, "StartWindow": { "Start": { + "Days": 60, "TimeUnitValue": 60, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -240,6 +247,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": 1 @@ -266,6 +274,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -285,6 +294,7 @@ ], "CensoringCriteria": [], "CollapseSettings": { + "EraPad": 0, "CollapseType": "ERA", "EraPadUnit": "day", "EraPadUnitValue": 0 diff --git a/src/test/resources/printfriendly/drugEra.json b/src/test/resources/printfriendly/drugEra.json index 6f8247bc..c46b4815 100644 --- a/src/test/resources/printfriendly/drugEra.json +++ b/src/test/resources/printfriendly/drugEra.json @@ -41,8 +41,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -159,6 +160,7 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -181,6 +183,7 @@ ], "CensoringCriteria": [], "CollapseSettings": { + "EraPad": 0, "CollapseType": "ERA", "EraPadUnit": "day", "EraPadUnitValue": 0 diff --git a/src/test/resources/printfriendly/drugExposure.json b/src/test/resources/printfriendly/drugExposure.json index b2ad53e5..7caadd91 100644 --- a/src/test/resources/printfriendly/drugExposure.json +++ b/src/test/resources/printfriendly/drugExposure.json @@ -250,6 +250,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/measurement.json b/src/test/resources/printfriendly/measurement.json index 0f5f0bc7..e2001a23 100644 --- a/src/test/resources/printfriendly/measurement.json +++ b/src/test/resources/printfriendly/measurement.json @@ -41,8 +41,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -60,8 +61,9 @@ }, "StartWindow": { "Start": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -264,7 +266,8 @@ "CollapseSettings": { "CollapseType": "ERA", "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPadUnitValue": 0, + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/noCensorCriteria.json b/src/test/resources/printfriendly/noCensorCriteria.json index dd045db5..bfdd20d4 100644 --- a/src/test/resources/printfriendly/noCensorCriteria.json +++ b/src/test/resources/printfriendly/noCensorCriteria.json @@ -398,6 +398,7 @@ ], "CollapseSettings":{ "CollapseType":"ERA", + "EraPad":0, "EraPadUnit": "days", "EraPadUnitValue": 0 } diff --git a/src/test/resources/printfriendly/nullCodesetId.json b/src/test/resources/printfriendly/nullCodesetId.json index 03ec724a..81fb082f 100644 --- a/src/test/resources/printfriendly/nullCodesetId.json +++ b/src/test/resources/printfriendly/nullCodesetId.json @@ -376,6 +376,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/observation.json b/src/test/resources/printfriendly/observation.json index 561af4c1..ae536e6a 100644 --- a/src/test/resources/printfriendly/observation.json +++ b/src/test/resources/printfriendly/observation.json @@ -41,6 +41,7 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": -1 @@ -217,6 +218,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/observationPeriod_1.json b/src/test/resources/printfriendly/observationPeriod_1.json index a55605eb..450124eb 100644 --- a/src/test/resources/printfriendly/observationPeriod_1.json +++ b/src/test/resources/printfriendly/observationPeriod_1.json @@ -35,6 +35,7 @@ }, "StartWindow": { "Start": { + "Days": 1, "TimeUnitValue": 1, "TimeUnit": "day", "Coeff": 1 @@ -120,6 +121,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/procedureOccurrence.json b/src/test/resources/printfriendly/procedureOccurrence.json index 5118c603..f3881bf4 100644 --- a/src/test/resources/printfriendly/procedureOccurrence.json +++ b/src/test/resources/printfriendly/procedureOccurrence.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -191,6 +192,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/specimen.json b/src/test/resources/printfriendly/specimen.json index f577d458..dd35d186 100644 --- a/src/test/resources/printfriendly/specimen.json +++ b/src/test/resources/printfriendly/specimen.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 1, "TimeUnitValue": 1, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -176,6 +177,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/visit.json b/src/test/resources/printfriendly/visit.json index a6296603..6660dda0 100644 --- a/src/test/resources/printfriendly/visit.json +++ b/src/test/resources/printfriendly/visit.json @@ -40,8 +40,9 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, - "TimeUnit": "day", + "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -177,6 +178,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json index 211b685c..20ba6752 100644 --- a/src/test/resources/printfriendly/visitDetail.json +++ b/src/test/resources/printfriendly/visitDetail.json @@ -40,6 +40,7 @@ "Coeff": -1 }, "End": { + "Days": 0, "TimeUnitValue": 0, "TimeUnit": "day", "Coeff": -1 @@ -109,6 +110,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", + "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, From 8a4eb23d88a2e87022d0a22cea237036ff40db54 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Mon, 8 Jan 2024 20:06:44 +0700 Subject: [PATCH 12/25] add sql test --- .../circe/check/checkers/Comparisons.java | 24 +- .../CohortExpressionQueryBuilder.java | 34 +- .../cohortdefinition/CollapseSettings.java | 2 +- .../cohortdefinition/DateOffsetStrategy.java | 11 +- .../circe/cohortdefinition/IntervalUnit.java | 2 - .../ohdsi/circe/cohortdefinition/Window.java | 25 +- .../printfriendly/criteriaTypes.ftl | 4 +- .../printfriendly/endStrategyTypes.ftl | 6 + .../printfriendly/inputTypes.ftl | 2 +- .../sql/customEraStrategy.sql | 2 +- .../circe/check/checkers/ComparisonsTest.java | 6 - .../checkers/CriteriaCheckValueTest.java | 46 +- .../checkers/DeathTimeWindowCheckTest.java | 8 +- .../CohortGeneration_5_0_0_Test.java | 77 ++- .../builders/CriteriaUtils.java | 18 + .../builders/WindowCriteria_5_0_0_Test.java | 16 + .../printfriendly/PrintFriendlyTest.java | 36 +- ...tionalCriteriaCheckValueCorrectMinute.json | 562 ++++++++++++++++++ ...additionalCriteriaCheckValueIncorrect.json | 3 +- ...tionalCriteriaCheckValueIncorrectHour.json | 329 ++++++++++ .../deathTimeWindowCheckCorrectSecond.json | 140 +++++ .../censorWindow/censorWindowExpression.json | 8 +- .../groupExpressionHour.json | 169 ++++++ .../eraDupes/eraDupesExpression.json | 2 +- .../continuousExposureCensorExpression.json | 1 - .../exits/continuousExposureExpression.json | 3 - .../exits/continuousExposure_PREP.json | 4 +- .../firstOccurrenceTestExpression.json | 1 - .../limits/limitExpression.json | 10 +- .../limits/limitTest_VERIFY.json | 40 +- .../printfriendly/countDistinctCriteria.json | 14 +- ...tDistinctCriteriaWithTimeUnitInterval.json | 181 ++++++ .../printfriendly/customEraExitHour.json | 60 ++ .../printfriendly/customEraExitMinute.json | 60 ++ .../printfriendly/customEraExitSecond.json | 60 ++ .../resources/printfriendly/visitDetail.json | 6 +- 36 files changed, 1840 insertions(+), 132 deletions(-) create mode 100644 src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json create mode 100644 src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json create mode 100644 src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json create mode 100644 src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json create mode 100644 src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json create mode 100644 src/test/resources/printfriendly/customEraExitHour.json create mode 100644 src/test/resources/printfriendly/customEraExitMinute.json create mode 100644 src/test/resources/printfriendly/customEraExitSecond.json diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 8bffe26e..8f54990c 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -107,19 +107,19 @@ public static Predicate compare(Concept source) { .build(); } - public static int compareTo(ObservationFilter filter, Window window) { - int range1, range2Start, range2End; - if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) && window.start.days != 0)) { - range1 = filter.postDays + filter.priorDays; - range2Start = window.start.coeff * window.start.days; - range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; - } else { - range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; - range2Start = getTimeInSeconds(window.start); - range2End = getTimeInSeconds(window.end); - } - return range1 - (range2End - range2Start); + public static int compareTo(ObservationFilter filter, Window window) { + int range1, range2Start, range2End; + if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) || IntervalUnit.DAY.getName().equals(window.start.timeUnit))) { + range1 = filter.postDays + filter.priorDays; + range2Start = window.start.coeff * window.start.days; + range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; + } else { + range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; + range2Start = getTimeInSeconds(window.start); + range2End = getTimeInSeconds(window.end); } + return range1 - (range2End - range2Start); + } private static int getTimeInSeconds(Window.Endpoint endpoint) { return Optional.ofNullable(endpoint) .map(ep -> { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index e22b520a..883b440a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -350,12 +350,12 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ resultSql = StringUtils.replace(resultSql, "@cohort_end_unions", StringUtils.join(endDateSelects, "\nUNION ALL\n")); - if(!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && expression.collapseSettings.eraPad != 0){ - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); - resultSql = StringUtils.replace(resultSql, "@era_pad_unit", "day"); - }else{ - resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); - resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + if (!StringUtils.isEmpty(Integer.toString(expression.collapseSettings.eraPad)) && (expression.collapseSettings.eraPadUnit == null || IntervalUnit.DAY.getName().equals(expression.collapseSettings.eraPadUnit))) { + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPad)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); + } else { + resultSql = StringUtils.replace(resultSql, "@eraconstructorpad", Integer.toString(expression.collapseSettings.eraPadUnitValue)); + resultSql = StringUtils.replace(resultSql, "@era_pad_unit", expression.collapseSettings.eraPadUnit); } resultSql = StringUtils.replace(resultSql, "@inclusionRuleTable", getInclusionRuleTableSql(expression)); resultSql = StringUtils.replace(resultSql, "@inclusionImpactAnalysisByEventQuery", getInclusionAnalysisQuery("#qualified_events", 0)); @@ -550,7 +550,7 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit Window startWindow = criteria.startWindow; String startIndexDateExpression = (startWindow.useIndexEnd != null && startWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; String startEventDateExpression = (startWindow.useEventEnd != null && startWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (startWindow.start.days != null) { + if (startWindow.start.days != null && (startWindow.start.timeUnit == null || startWindow.start.timeUnit.equals(IntervalUnit.DAY.getName()))) { startExpression = String.format("DATEADD(day,%d,%s)", startWindow.start.coeff * startWindow.start.days, startIndexDateExpression); } else if (startWindow.start.timeUnitValue != null) { startExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.start.coeff * startWindow.start.timeUnitValue, startIndexDateExpression); @@ -776,12 +776,12 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - if(strat.offset != 0){ - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); - strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); - }else { - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); - strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + if (strat.offsetUnit == null || strat.offsetUnit.equals(IntervalUnit.DAY.getName())) { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); + } else { + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); } strategySql = StringUtils.replace(strategySql, "@dateField", getDateFieldForOffsetStrategy(strat.dateField)); @@ -802,15 +802,15 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { String strategySql = StringUtils.replace(CUSTOM_ERA_STRATEGY_TEMPLATE, "@eventTable", eventTable); strategySql = StringUtils.replace(strategySql, "@drugCodesetId", strat.drugCodesetId.toString()); if (IntervalUnit.DAY.getName().equals(strat.gapUnit)) { - strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); } else { - strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); + strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); } strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); if (IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); } else { - strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); } strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); strategySql = StringUtils.replace(strategySql, "@drugExposureEndDateExpression", drugExposureEndDateExpression); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index c9d44214..2fcfc1f5 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -29,7 +29,7 @@ public class CollapseSettings { @JsonProperty("EraPad") public int eraPad = 0; @JsonProperty("EraPadUnit") - public String eraPadUnit = "day"; + public String eraPadUnit = IntervalUnit.DAY.getName(); @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 7e5e27ba..476f2bce 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -22,21 +22,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * * @author Chris Knoll */ @JsonIgnoreProperties(ignoreUnknown = true) public class DateOffsetStrategy extends EndStrategy { - public enum DateField { - StartDate, - EndDate - } @JsonProperty("Offset") public int offset = 0; @JsonProperty("DateField") public DateField dateField = DateField.StartDate; - @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; @JsonProperty("OffsetUnit") @@ -46,4 +40,9 @@ public enum DateField { public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { return dispatcher.getStrategySql(this, eventTable); } + + public enum DateField { + StartDate, + EndDate + } } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java index e55b58e7..18da37c2 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IntervalUnit.java @@ -1,7 +1,5 @@ package org.ohdsi.circe.cohortdefinition; -import jdk.nashorn.internal.objects.annotations.Getter; - public enum IntervalUnit { DAY("day"), HOUR("hour"), diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index 22be0794..5667d448 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -22,12 +22,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * * @author cknoll1 */ @JsonIgnoreProperties(ignoreUnknown = true) public class Window { + @JsonProperty("Start") + public Endpoint start; + @JsonProperty("End") + public Endpoint end; + @JsonProperty("UseIndexEnd") + public Boolean useIndexEnd; + @JsonProperty("UseEventEnd") + public Boolean useEventEnd; + public static class Endpoint { @JsonProperty("Days") @@ -35,22 +43,9 @@ public static class Endpoint { @JsonProperty("TimeUnitValue") public Integer timeUnitValue; @JsonProperty("TimeUnit") - public String timeUnit="day"; + public String timeUnit = "day"; @JsonProperty("Coeff") public int coeff; } - - @JsonProperty("Start") - public Endpoint start; - - @JsonProperty("End") - public Endpoint end; - - @JsonProperty("UseIndexEnd") - public Boolean useIndexEnd; - - @JsonProperty("UseEventEnd") - public Boolean useEventEnd; - } diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index f1ec4503..56d3cbde 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -293,8 +293,8 @@ group.demographicCriteriaList?size == 1><@DemographicCriteria c=group.demographi --><#if countCriteria.occurrence.isDistinct> distinct <@utils.countColumn countCriteria.occurrence.countColumn!"DOMAIN_CONCEPT" /> from <@Criteria c=countCriteria.criteria level=level isPlural=(countCriteria.occurrence.count != 1) countCriteria=countCriteria indexLabel=indexLabel /> <#macro WindowCriteria countCriteria indexLabel="cohort entry" level=0><#local windowParts=[] restrictParts=[]><#if -countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if -countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue??)> +countCriteria.startWindow.start.timeUnitValue?? || countCriteria.startWindow.end.timeUnitValue?? || countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??><#local temp><@inputTypes.Window countCriteria.startWindow indexLabel /><#local windowParts+=[temp]><#if +countCriteria.endWindow?? && (countCriteria.endWindow.start.timeUnitValue?? || countCriteria.endWindow.end.timeUnitValue?? || countCriteria.startWindow.start.days?? || countCriteria.startWindow.end.days??)> <#local temp><@inputTypes.Window countCriteria.endWindow indexLabel /><#local windowParts+=[temp]> <#if countCriteria.restrictVisit!false><#local temp>at same visit as ${indexLabel}<#local restrictParts+=[temp]> <#if countCriteria.ignoreObservationPeriod!false><#local temp>allow events outside observation period<#local restrictParts+=[temp]><#if diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl index 1fad6ffb..651883f8 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/endStrategyTypes.ftl @@ -28,7 +28,13 @@ The person exits the cohort at the end of continuous observation. <#macro CustomEraStrategy s> + <#if s.offsetUnit == "day"> The cohort end date will be based on a continuous exposure to ${utils.codesetName(s.drugCodesetId!"", "_invalid drug specified_")}: allowing ${s.gapDays} days between exposures, adding <@utils.formatValue s.offset "day"/> after exposure ends, and <#if s.daysSupplyOverride??>forcing drug exposure days supply to: <@utils.formatValue s.daysSupplyOverride "day"/>.<#else>using days supply and exposure end date for exposure duration. + <#else > +The cohort end date will be based on a continuous exposure to ${utils.codesetName(s.drugCodesetId!"", "_invalid drug specified_")}: +allowing ${s.gapUnitValue} ${s.offsetUnit} between exposures, adding <@utils.formatValue s.offsetUnitValue s.offsetUnit/> after exposure ends, and <#if +s.daysSupplyOverride??>forcing drug exposure ${s.offsetUnit} supply to: <@utils.formatValue s.daysSupplyOverride s.offsetUnit/>.<#else>using ${s.offsetUnit} supply and exposure end date for exposure duration. + diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 3c2fe52a..6914b569 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -118,7 +118,7 @@ item?counter == list?size> or <#else>, ${item} <#-- User Defined Period --> -<#macro UserDefinedPeriod p><#if +<#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index f70ad927..3bde650c 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -26,7 +26,7 @@ INTO #strategy_ends from @eventTable et JOIN ( - select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(@offsetUnit, @offsetUnitValue, ENDS.era_end_date) as era_end_date + select ENDS.person_id, min(drug_exposure_start_date) as era_start_date, DATEADD(day, @offsetUnitValue, ENDS.era_end_date) as era_end_date from ( select de.person_id, de.drug_exposure_start_date, MIN(e.END_DATE) as era_end_date diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 0bafd06c..4ddeb5e8 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -147,12 +147,10 @@ public void compareToHours() { Window window = new Window(); window.start = new Window.Endpoint(); window.end = new Window.Endpoint(); - window.start.days = 0; window.start.timeUnit = "hour"; window.start.timeUnitValue = 72; window.start.coeff = -1; - window.end.days = 0; window.end.timeUnit = "hour"; window.end.timeUnitValue = 120; window.end.coeff = 1; @@ -168,12 +166,10 @@ public void compareToMinute() { Window window = new Window(); window.start = new Window.Endpoint(); window.end = new Window.Endpoint(); - window.start.days = 0; window.start.timeUnit = "minute"; window.start.timeUnitValue = 30; window.start.coeff = -1; - window.end.days = 0; window.end.timeUnit = "minute"; window.end.timeUnitValue = 75; window.end.coeff = 1; @@ -190,12 +186,10 @@ public void compareToSecond() { Window window = new Window(); window.start = new Window.Endpoint(); window.end = new Window.Endpoint(); - window.start.days = 0; window.start.timeUnit = "second"; window.start.timeUnitValue = 30; window.start.coeff = -1; - window.end.days = 0; window.end.timeUnit = "second"; window.end.timeUnitValue = 75; window.end.coeff = 1; diff --git a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java index 062ef9ae..905c1625 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java @@ -12,47 +12,51 @@ public class CriteriaCheckValueTest { private static final CohortExpression WRONG_PRIMARY_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueIncorrect.json")); private static final CohortExpression CORRECT_PRIMARY_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/primaryCriteriaCheckValueCorrect.json")); private static final CohortExpression WRONG_ADDITIONAL_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrect.json")); + private static final CohortExpression WRONG_ADDITIONAL_EXPRESSION_HOUR = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueIncorrectHour.json")); private static final CohortExpression CORRECT_ADDITIONAL_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrect.json")); + private static final CohortExpression CORRECT_ADDITIONAL_EXPRESSION_MINUTE = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/additionalCriteriaCheckValueCorrectMinute.json")); private static final CohortExpression WRONG_INCLUSION_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueIncorrect.json")); private static final CohortExpression CORRECT_INCLUSION_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/inclusionRulesCheckValueCorrect.json")); private static final CohortExpression WRONG_CENSORING_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueIncorrect.json")); private static final CohortExpression CORRECT_CENSORING_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/censoringEventCheckValueCorrect.json")); private static final CohortExpression WRONG_EMPTY_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckIncorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckIncorrect.json")); private static final CohortExpression CORRECT_EMPTY_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckCorrect.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyDemographicCheckCorrect.json")); private static final CohortExpression EMPTY_CENSORING_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCensoringCriteriaList.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCensoringCriteriaList.json")); private static final CohortExpression EMPTY_PRIMARY_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyPrimaryCriteriaList.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyPrimaryCriteriaList.json")); private static final CohortExpression EMPTY_INCLUSION_RULES_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyInclusionRules.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyInclusionRules.json")); private static final CohortExpression EMPTY_CORRELATED_CRITERIA_EXPRESSION = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCorrelatedCriteria.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/emptyCorrelatedCriteria.json")); private static final CohortExpression NO_EXIT_CRITERIA_CHECK = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheck.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheck.json")); private static final CohortExpression NO_EXIT_CRITERIA_CHECK_EARLIEST_EVENT = - CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheckEarliestEvent.json")); + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheckEarliestEvent.json")); private static final int RANGE_PRIMARY_WARNING_COUNT = 148; private static final int CONCEPT_PRIMARY_WARNING_COUNT = 61; @@ -119,12 +123,22 @@ public void checkAdditionalRangeIncorrect() { List warnings = rangeCheck.check(WRONG_ADDITIONAL_EXPRESSION); assertEquals(RANGE_ADDITIONAL_WARNING_COUNT, warnings.size()); } + @Test + public void checkAdditionalRangeIncorrectHour() { + List warnings = rangeCheck.check(WRONG_ADDITIONAL_EXPRESSION_HOUR); + assertEquals(RANGE_ADDITIONAL_WARNING_COUNT, warnings.size()); + } @Test public void checkAdditionalRangeCorrect() { List warnings = rangeCheck.check(CORRECT_ADDITIONAL_EXPRESSION); assertEquals(Collections.emptyList(), warnings); } + @Test + public void checkAdditionalRangeCorrectMinute() { + List warnings = rangeCheck.check(CORRECT_ADDITIONAL_EXPRESSION_MINUTE); + assertEquals(Collections.emptyList(), warnings); + } @Test public void checkAdditionalConceptIncorrect() { diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java index bdfd11c1..fe42e08f 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java @@ -14,6 +14,8 @@ public class DeathTimeWindowCheckTest { CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckIncorrect.json")); private static final CohortExpression CORRECT_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckCorrect.json")); + private static final CohortExpression CORRECT_EXPRESSION_SECOND = + CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/deathTimeWindowCheckCorrectSecond.json")); private static final int WARNING_COUNT = 2; @@ -24,10 +26,14 @@ public void checkIncorrect() { List warnings = check.check(INCORRECT_EXPRESSION); assertEquals(WARNING_COUNT, warnings.size()); } - @Test public void checkCorrect() { List warnings = check.check(CORRECT_EXPRESSION); assertEquals(0, warnings.size()); } + @Test + public void checkCorrectSecond() { + List warnings = check.check(CORRECT_EXPRESSION_SECOND); + assertEquals(0, warnings.size()); + } } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java index 06d8652a..6c437421 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java @@ -7,7 +7,6 @@ import java.lang.reflect.Field; import java.sql.SQLException; import com.github.mjeanroy.dbunit.core.dataset.DataSetFactory; -import org.apache.commons.lang3.StringUtils; import org.dbunit.Assertion; import org.dbunit.DatabaseUnitException; import org.dbunit.database.IDatabaseConnection; @@ -359,6 +358,82 @@ public void testGroups() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedTable, actualTable); } + @Test + public void testGroupsHour() throws Exception { + final String RESULTS_SCHEMA = "groupsTest"; + final String[] testDataSetsPrep = new String[] { + "/datasets/vocabulary.json", + "/cohortgeneration/correlatedCriteria/groupTest_PREP.json" + }; + final IDatabaseConnection dbUnitCon = getConnection(); + + // prepare results schema for the specified options.resultSchema + prepareSchema(RESULTS_SCHEMA, RESULTS_DDL_PATH); + + // load test data into DB. + final IDataSet dsPrep = DataSetFactory.createDataSet(testDataSetsPrep); + DatabaseOperation.CLEAN_INSERT.execute(dbUnitCon, dsPrep); // clean load of the DB. Careful, clean means "delete the old stuff" + + CohortExpressionQueryBuilder.BuildExpressionQueryOptions options; + CohortExpression expression; + String cohortSql; + + // this expression has groups defined as: (A OR B OR (C AND D)) + // Cohort 1: Persons 1,2,3 satisfy these criteria through different branches + expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/cohortgeneration/correlatedCriteria/groupExpressionHour.json")); + options = buildExpressionQueryOptions(1, RESULTS_SCHEMA); + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // Cohort 2: Change outer group to 'at least 2' + // person 1 satisfies A, B and (C AND D) + expression.additionalCriteria.type = "AT_LEAST"; + expression.additionalCriteria.count = 2; + options.cohortId = 2; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // cohort 3: change outer group to 'at most 1' + // person 2 and 3 only have 1: 2 has A and 3 has (C AND D) + expression.additionalCriteria.type = "AT_MOST"; + expression.additionalCriteria.count = 2; + options.cohortId = 3; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + + // cohort 4: outer group 'ALL', change inner group to 'at most 0' + // No one passes, since that arrangement is a contradiction + expression.additionalCriteria.type = "ALL"; + expression.additionalCriteria.groups[0].type = "AT_MOST"; + expression.additionalCriteria.groups[0].count = 0; + options.cohortId = 4; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // cohort 5: remove inner group, an empty group should have no impact + // Person 1 has both A and B + expression.additionalCriteria.groups = new CriteriaGroup[0]; + options.cohortId = 5; + cohortSql = buildExpressionSql(expression, options); + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + + // Validate results + // Load actual records from cohort table + final ITable actualTable = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".cohort", String.format("SELECT * from %s ORDER BY cohort_definition_id, subject_id, cohort_start_date", RESULTS_SCHEMA + ".cohort")); + // Load expected data from an XML dataset + final String[] testDataSetsVerify = new String[] {"/cohortgeneration/correlatedCriteria/groupTest_VERIFY.json"}; + final IDataSet expectedDataSet = DataSetFactory.createDataSet(testDataSetsVerify); + final ITable expectedTable = expectedDataSet.getTable(RESULTS_SCHEMA + ".cohort"); + + // Assert actual database table match expected table + Assertion.assertEquals(expectedTable, actualTable); + } /** diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index 83f14077..37dd6a6a 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -35,10 +35,28 @@ public static Window getPrior365Window() { // ... and 0 days before Window.Endpoint endPoint = new Window.Endpoint(); endPoint.coeff = -1; + endPoint.days = 0; endPoint.timeUnitValue = 0; prior365Window.end = endPoint; return prior365Window; } + + public static Window getPriorHoursWindow() { + Window prior365Window = new Window(); + // index starts between 365d before + Window.Endpoint startPoint = new Window.Endpoint(); + startPoint.coeff = -1; + startPoint.days = null; + startPoint.timeUnitValue = 8760; + startPoint.timeUnit = "hour"; + prior365Window.start = startPoint; + // ... and 0 days before + Window.Endpoint endPoint = new Window.Endpoint(); + endPoint.coeff = -1; + endPoint.timeUnitValue = 0; + prior365Window.end = endPoint; + return prior365Window; + } public static Window getAnyTimeWindow() { Window anytimeWindow = new Window(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java index 5988a172..fc863525 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java @@ -176,6 +176,22 @@ public void windowDoseEraTest() throws Exception { } + @Test + public void windowDoseEraTestHour() throws Exception { + + final String resultsSchema = "window_dose_era"; + final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", + "/windowcriteria/windowDoseEra_PREP.json"}; + final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowDoseEra_VERIFY.json"}; + WindowedCriteria wc = new WindowedCriteria(); + wc.criteria = new DoseEra(); // find any dose era + wc.startWindow = CriteriaUtils.getPriorHoursWindow(); + List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, + CriteriaColumn.VALUE_AS_NUMBER, CriteriaColumn.UNIT, CriteriaColumn.DURATION); + this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); + + } + @Test public void windowDrugEraTest() throws Exception { diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index 47c41559..49ea1f26 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -455,6 +455,36 @@ public void customEraExitTest() { )); } + @Test + public void customEraExitTestHour() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitHour.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 336 hour between exposures, adding 24 hours after exposure ends, and forcing drug exposure hour supply to: 168 hours." + )); + + } + @Test + public void customEraExitTestMinute() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitMinute.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 20,160 minute between exposures, adding 1,440 minutes after exposure ends, and forcing drug exposure minute supply to: 10,080 minutes." + )); + + } + @Test + public void customEraExitTestSecond() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/customEraExitSecond.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + "The cohort end date will be based on a continuous exposure to 'Concept Set 1':", + "allowing 1,209,600 second between exposures, adding 86,400 seconds after exposure ends, and forcing drug exposure second supply to: 604,800 seconds." + )); + + } @Test public void conceptSetSimpleTest() { @@ -625,21 +655,21 @@ public void nullCodesetIdTest() { public void nullCohortTest() { exceptionRule.expect(RuntimeException.class); pf.renderCohort((CohortExpression)null); - + } @Test public void nullConceptSetTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSet((ConceptSet)null); - + } @Test public void nullConceptSetListTest() { exceptionRule.expect(RuntimeException.class); pf.renderConceptSetList((ConceptSet[])null); - + } @Test diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json b/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json new file mode 100644 index 00000000..a1dac8bb --- /dev/null +++ b/src/test/resources/checkers/additionalCriteriaCheckValueCorrectMinute.json @@ -0,0 +1,562 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Aspirin", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "1191", + "CONCEPT_ID": 1112807, + "CONCEPT_NAME": "Aspirin", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "OccurrenceStartDate": { + "Value": "2020-01-03", + "Op": "lt" + }, + "MeasurementType": [ + { + "CONCEPT_CODE": "10221000000101", + "CONCEPT_ID": 44787754, + "CONCEPT_NAME": "FP70A postgraduate training allowance form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Operator": [ + { + "CONCEPT_CODE": "276139006", + "CONCEPT_ID": 4171756, + "CONCEPT_NAME": "<", + "DOMAIN_ID": "Meas Value Operator", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ValueAsNumber": { + "Value": 3, + "Op": "gt" + }, + "ValueAsConcept": [ + { + "CONCEPT_CODE": "17511009", + "CONCEPT_ID": 4068402, + "CONCEPT_NAME": "Normal absence", + "DOMAIN_ID": "Meas Value", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Unit": [ + { + "CONCEPT_CODE": "%{abnormal}", + "CONCEPT_ID": 9216, + "CONCEPT_NAME": "percent abnormal", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "RangeLow": { + "Value": 2, + "Op": "gt" + }, + "RangeHigh": { + "Value": 2, + "Op": "gt" + }, + "RangeLowRatio": { + "Value": 2, + "Op": "gt" + }, + "RangeHighRatio": { + "Value": 2, + "Op": "gt" + }, + "Age": { + "Value": 2, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "703118005", + "CONCEPT_ID": 45766035, + "CONCEPT_NAME": "Feminine gender", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "101YS0200X", + "CONCEPT_ID": 38003628, + "CONCEPT_NAME": "Behavioral Health & Social Service Providers, Counselor, School", + "DOMAIN_ID": "Provider Specialty", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "OP", + "CONCEPT_ID": 9202, + "CONCEPT_NAME": "Outpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "1440", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DoseEra": { + "EraStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "EraEndDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "DoseValue": { + "Value": 2, + "Op": "gt" + }, + "EraLength": { + "Value": 3, + "Op": "gt" + }, + "AgeAtStart": { + "Value": 4, + "Op": "gt" + }, + "AgeAtEnd": { + "Value": 5, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407378000", + "CONCEPT_ID": 4251434, + "CONCEPT_NAME": "Surgically transgendered transsexual, male-to-female", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "2880", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DeviceExposure": { + "UniqueDeviceId": { + "Text": "13", + "Op": "contains" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 2, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "394744001", + "CONCEPT_ID": 4215271, + "CONCEPT_NAME": "Gender unspecified", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.01", + "CONCEPT_ID": 38003574, + "CONCEPT_NAME": "Asian Indian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-02", + "Op": "lt" + } + } + ], + "Groups": [] + }, + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-01", + "Op": "lt" + }, + "ConditionType": [ + { + "CONCEPT_CODE": "10251000000106", + "CONCEPT_ID": 45770878, + "CONCEPT_NAME": "FP75 leave payment form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "StopReason": { + "Text": "stop", + "Op": "contains" + }, + "Age": { + "Value": 1, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "703117000", + "CONCEPT_ID": 45766034, + "CONCEPT_NAME": "Masculine gender", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "LTCP", + "CONCEPT_ID": 42898160, + "CONCEPT_NAME": "Long Term Care Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "4320", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 2, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407379008", + "CONCEPT_ID": 4231242, + "CONCEPT_NAME": "Surgically transgendered transsexual, female-to-male", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.01", + "CONCEPT_ID": 38003574, + "CONCEPT_NAME": "Asian Indian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2020-01-01", + "Op": "lt" + } + } + ], + "Groups": [] + }, + "EraStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "EraEndDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceCount": { + "Value": 1, + "Op": "lt" + }, + "EraLength": { + "Value": 1, + "Op": "lt" + }, + "AgeAtStart": { + "Value": 2, + "Op": "lt" + }, + "AgeAtEnd": { + "Value": 2, + "Op": "lt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407375002", + "CONCEPT_ID": 4234363, + "CONCEPT_NAME": "Surgically transgendered transsexual", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ] + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "5460", + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "timeUnitValue": "0", + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 1, + "Op": "gt" + }, + "Gender": [ + { + "CONCEPT_CODE": "407375002", + "CONCEPT_ID": 4234363, + "CONCEPT_NAME": "Surgically transgendered transsexual", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2.02", + "CONCEPT_ID": 38003575, + "CONCEPT_NAME": "Bangladeshi", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Hispanic", + "CONCEPT_ID": 38003563, + "CONCEPT_NAME": "Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Op": "lt" + }, + "OccurrenceEndDate": { + "Value": "2019-12-31", + "Op": "lt" + } + } + ], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 1 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=6.1.0" +} \ No newline at end of file diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json index 266cc3e9..c85cfe33 100644 --- a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrect.json @@ -310,8 +310,7 @@ "CollapseSettings": { "CollapseType": "ERA", "EraPadUnit": "hour", - "EraPadUnitValue": 0, - "EraPad": 0 + "EraPadUnitValue": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=6.1.0" diff --git a/src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json new file mode 100644 index 00000000..9c69f542 --- /dev/null +++ b/src/test/resources/checkers/additionalCriteriaCheckValueIncorrectHour.json @@ -0,0 +1,329 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Aspirin", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "1191", + "CONCEPT_ID": 1112807, + "CONCEPT_NAME": "Aspirin", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": -3, + "PostDays": -2 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "OccurrenceStartDate": { + "Op": "lt" + }, + "MeasurementType": [], + "Operator": [], + "ValueAsNumber": { + "Op": "gt" + }, + "ValueAsConcept": [], + "Unit": [], + "RangeLow": { + "Op": "gt" + }, + "RangeHigh": { + "Op": "gt" + }, + "RangeLowRatio": { + "Op": "gt" + }, + "RangeHighRatio": { + "Op": "gt" + }, + "Age": { + "Op": "gt" + }, + "Gender": [], + "ProviderSpecialty": [], + "VisitType": [] + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DeviceExposure": { + "UniqueDeviceId": { + "Op": "contains" + } + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 24, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Op": "gt" + }, + "Gender": [], + "Race": [], + "Ethnicity": [], + "OccurrenceStartDate": { + "Op": "lt" + }, + "OccurrenceEndDate": { + "Op": "lt" + } + } + ], + "Groups": [] + }, + "OccurrenceStartDate": { + "Op": "lt" + }, + "OccurrenceEndDate": { + "Op": "lt" + }, + "ConditionType": [], + "StopReason": { + "Op": "contains" + }, + "Age": { + "Op": "lt" + }, + "Gender": [], + "ProviderSpecialty": [], + "VisitType": [] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 24, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DoseEra": { + "EraStartDate": { + "Op": "lt" + }, + "EraEndDate": { + "Op": "lt" + }, + "Unit": [], + "DoseValue": { + "Op": "gt" + }, + "EraLength": { + "Op": "gt" + }, + "AgeAtStart": { + "Op": "gt" + }, + "AgeAtEnd": { + "Op": "gt" + }, + "Gender": [] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 12, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Op": "lt" + }, + "Gender": [], + "Race": [], + "Ethnicity": [], + "OccurrenceStartDate": { + "Op": "lt" + }, + "OccurrenceEndDate": { + "Op": "lt" + } + } + ], + "Groups": [] + }, + "EraStartDate": { + "Value": "2020-01-16", + "Extent": "2020-01-16", + "Op": "bt" + }, + "EraEndDate": { + "Op": "lt" + }, + "OccurrenceCount": { + "Value": -5, + "Op": "lt" + }, + "EraLength": { + "Op": "lt" + }, + "AgeAtStart": { + "Op": "lt" + }, + "AgeAtEnd": { + "Op": "lt" + }, + "Gender": [] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": -72, + "timeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": -48, + "timeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 5, + "Extent": 1, + "Op": "bt" + }, + "Gender": [], + "Race": [], + "Ethnicity": [], + "OccurrenceStartDate": { + "Value": "2020-01-03", + "Extent": "2020-01-32", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Op": "lt" + } + } + ], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=6.1.0" +} \ No newline at end of file diff --git a/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json b/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json new file mode 100644 index 00000000..906e64e6 --- /dev/null +++ b/src/test/resources/checkers/deathTimeWindowCheckCorrectSecond.json @@ -0,0 +1,140 @@ +{ + "ConceptSets": [ + { + "id": 2, + "name": "ibuprofen", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Ingredient", + "CONCEPT_CODE": "5640", + "CONCEPT_ID": 1177480, + "CONCEPT_NAME": "Ibuprofen", + "DOMAIN_ID": "Drug", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "RxNorm" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CodesetId": 2 + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "604800", + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 2 + } + } + ], + "ObservationWindow": { + "PriorDays": 14, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": {} + }, + "StartWindow": { + "Start": { + "Days": 7, + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + } + }, + "StartWindow": { + "Start": { + "timeUnitValue": "1209600", + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "QualifiedLimit": { + "Type": "All" + }, + "ExpressionLimit": { + "Type": "Last" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.3.0" +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json index bf25e20e..5a8056ea 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json @@ -50,15 +50,15 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "OffsetUnit": "day", - "OffsetUnitValue": 90 + "OffsetUnitValue": 90, + "OffsetUnit": "day" } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPadUnitValue": 0, + "EraPadUnit": "day" }, "CensorWindow": { "StartDate": "2000-04-01", diff --git a/src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json new file mode 100644 index 00000000..82d80ba3 --- /dev/null +++ b/src/test/resources/cohortgeneration/correlatedCriteria/groupExpressionHour.json @@ -0,0 +1,169 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Parent Conceptset", + "expression": { + "items": [ + { + "concept": { + "CONCEPT_CLASS_ID": "Clinical Finding", + "CONCEPT_CODE": "P1", + "CONCEPT_ID": 1, + "CONCEPT_NAME": "Parent 1", + "DOMAIN_ID": "CONDITION", + "INVALID_REASON": "V", + "INVALID_REASON_CAPTION": "Valid", + "STANDARD_CONCEPT": "S", + "STANDARD_CONCEPT_CAPTION": "Standard", + "VOCABULARY_ID": "TestVocab" + }, + "includeDescendants": true + } + ] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "ConditionOccurrence": { + "CodesetId": 0 + } + } + ], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "All" + } + }, + "AdditionalCriteria": { + "Type": "ANY", + "CriteriaList": [ + { + "Criteria": { + "DrugExposure": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 720, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "Days": 0, + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "ProcedureOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 720, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DrugExposure": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 1440, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "ProcedureOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 1440, + "TimeUnit": "hour", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "hour", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + ] + }, + "QualifiedLimit": { + "Type": "All" + }, + "ExpressionLimit": { + "Type": "All" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {} +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 9c3b8584..543fa6d7 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -31,7 +31,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 30, + "EraPad": 43200, "EraPadUnit": "minute", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json index 2fb0db21..b5aebb73 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureCensorExpression.json @@ -61,7 +61,6 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index bd12cbe7..a064c47e 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -50,10 +50,8 @@ "EndStrategy": { "CustomEra": { "DrugCodesetId": 0, - "GapDays": 60, "GapUnit": "day", "GapUnitValue": 60, - "Offset": 0, "OffsetUnit": "day", "OffsetUnitValue": 0 } @@ -61,7 +59,6 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json b/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json index 549ac6ad..587e1dfe 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposure_PREP.json @@ -16,7 +16,7 @@ "observation_period_end_date":"2001-01-01", "period_type_concept_id": 0 } - ], + ], "cdm.drug_exposure": [ { "drug_exposure_id": 1, @@ -66,6 +66,6 @@ "drug_concept_id":2, "drug_exposure_start_date":"2000-10-01", "drug_type_concept_id":0 - } + } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json index 0713b0dc..e66c806f 100644 --- a/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json +++ b/src/test/resources/cohortgeneration/firstOccurrence/firstOccurrenceTestExpression.json @@ -44,7 +44,6 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, "EraPadUnit": "day", "EraPadUnitValue": 0 }, diff --git a/src/test/resources/cohortgeneration/limits/limitExpression.json b/src/test/resources/cohortgeneration/limits/limitExpression.json index b638424b..df0ce0ba 100644 --- a/src/test/resources/cohortgeneration/limits/limitExpression.json +++ b/src/test/resources/cohortgeneration/limits/limitExpression.json @@ -55,8 +55,6 @@ }, "End": { "Days": 1, - "TimeUnitValue": 1, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -80,17 +78,13 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "Offset": 1, - "OffsetUnit": "day", - "OffsetUnitValue": 1 + "Offset": 1 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 60, - "EraPadUnit": "day", - "EraPadUnitValue": 60 + "EraPad": 60 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json index 4f3fb3b6..ce44ba5f 100644 --- a/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json +++ b/src/test/resources/cohortgeneration/limits/limitTest_VERIFY.json @@ -1,16 +1,40 @@ { "limitTest.cohort": [ { - "cohort_definition_id": 1, - "subject_id": 1, - "cohort_start_date": "2000-02-01", - "cohort_end_date": "2000-06-02" + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2000-02-01", + "cohort_end_date":"2000-02-02" }, { - "cohort_definition_id": 1, - "subject_id": 1, - "cohort_start_date": "2000-07-01", - "cohort_end_date": "2000-10-02" + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2000-06-01", + "cohort_end_date":"2000-08-02" + }, + { + "cohort_definition_id":1, + "subject_id":1, + "cohort_start_date":"2000-11-01", + "cohort_end_date":"2000-12-02" + }, + { + "cohort_definition_id":2, + "subject_id":1, + "cohort_start_date":"2000-02-01", + "cohort_end_date":"2000-02-02" + }, + { + "cohort_definition_id":3, + "subject_id":1, + "cohort_start_date":"2000-02-01", + "cohort_end_date":"2000-02-02" + }, + { + "cohort_definition_id":5, + "subject_id":1, + "cohort_start_date":"2000-12-01", + "cohort_end_date":"2000-12-02" } ] } \ No newline at end of file diff --git a/src/test/resources/printfriendly/countDistinctCriteria.json b/src/test/resources/printfriendly/countDistinctCriteria.json index f5dbe6d8..69a6d669 100644 --- a/src/test/resources/printfriendly/countDistinctCriteria.json +++ b/src/test/resources/printfriendly/countDistinctCriteria.json @@ -31,14 +31,10 @@ "StartWindow": { "Start": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -82,8 +78,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -111,8 +105,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -136,8 +128,6 @@ "StartWindow": { "Start": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "End": { @@ -178,9 +168,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json b/src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json new file mode 100644 index 00000000..466358c7 --- /dev/null +++ b/src/test/resources/printfriendly/countDistinctCriteriaWithTimeUnitInterval.json @@ -0,0 +1,181 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Empty Concept Set", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "ConditionOccurrence": { + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "2010-01-01", + "Op": "gte" + } + } + }, + { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": {} + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 2592000, + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 720, + "TimeUnit": "hour", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "Age": { + "Value": 18, + "Extent": 64, + "Op": "bt" + } + } + }, + { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CodesetId": 0, + "Age": { + "Value": 18, + "Op": "gt" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "day", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1, + "CountColumn": "DOMAIN_CONCEPT" + } + }, + { + "Criteria": { + "ConditionOccurrence": { + "CodesetId": 0, + "Age": { + "Value": 18, + "Op": "gt" + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 0, + "TimeUnit": "day", + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1, + "CountColumn": "START_DATE" + } + }, + { + "Criteria": { + "ConditionOccurrence": { + "Age": { + "Value": 64, + "Op": "lt" + } + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 0, + "TimeUnit": "day", + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "IsDistinct": true, + "Type": 2, + "Count": 1, + "CountColumn": "VISIT_ID" + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0 + } + } + ], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "day", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitHour.json b/src/test/resources/printfriendly/customEraExitHour.json new file mode 100644 index 00000000..adc2d8c4 --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitHour.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 336, + "GapUnit": "hour", + "OffsetUnitValue": 24, + "OffsetUnit": "hour", + "DaysSupplyOverride": 168 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "hour", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitMinute.json b/src/test/resources/printfriendly/customEraExitMinute.json new file mode 100644 index 00000000..55001076 --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitMinute.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 20160, + "GapUnit": "minute", + "OffsetUnitValue": 1440, + "OffsetUnit": "minute", + "DaysSupplyOverride": 10080 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "minute", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/customEraExitSecond.json b/src/test/resources/printfriendly/customEraExitSecond.json new file mode 100644 index 00000000..6817a66b --- /dev/null +++ b/src/test/resources/printfriendly/customEraExitSecond.json @@ -0,0 +1,60 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Concept Set 1", + "expression": { + "items": [] + } + }, + { + "id": 1, + "name": "Concept Set 2", + "expression": { + "items": [] + } + }, + { + "id": 2, + "name": "Concept Set 3", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "EndStrategy": { + "CustomEra": { + "DrugCodesetId": 0, + "GapUnitValue": 1209600, + "GapUnit": "second", + "OffsetUnitValue": 86400, + "OffsetUnit": "second", + "DaysSupplyOverride": 604800 + } + }, + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnit": "second", + "EraPadUnitValue": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json index 20ba6752..a3dd827b 100644 --- a/src/test/resources/printfriendly/visitDetail.json +++ b/src/test/resources/printfriendly/visitDetail.json @@ -41,8 +41,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -110,9 +108,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.3.0" From f819d5e6d6f880cf558274d124e152ec38f73954 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Tue, 9 Jan 2024 18:04:52 +0700 Subject: [PATCH 13/25] fix --- .../checkers/ExitCriteriaDaysOffsetCheck.java | 10 ++-- .../checkers/FirstTimeInHistoryCheck.java | 8 +-- .../cohortdefinition/CustomEraStrategy.java | 10 +++- .../cohortdefinition/DateOffsetStrategy.java | 20 ++++--- .../ohdsi/circe/cohortdefinition/Window.java | 33 ++++++----- .../resources/SqlRender-1.15.0-SNAPSHOT.jar | Bin 0 -> 57903 bytes .../sql/dateOffsetStrategy.sql | 7 +-- .../cohortdefinition/sql/generateCohort.sql | 6 +- .../circe/check/checkers/ComparisonsTest.java | 52 +++++++++++++++++- .../checkers/DeathTimeWindowCheckTest.java | 10 ++-- .../printfriendly/PrintFriendlyTest.java | 10 ++-- 11 files changed, 111 insertions(+), 55 deletions(-) create mode 100644 src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index e4294203..1ee188c1 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -22,8 +22,6 @@ import org.ohdsi.circe.cohortdefinition.CohortExpression; import org.ohdsi.circe.cohortdefinition.DateOffsetStrategy; -import java.util.Arrays; -import java.util.List; import java.util.Objects; import static org.ohdsi.circe.check.operations.Operations.match; @@ -43,9 +41,9 @@ protected WarningSeverity defineSeverity() { protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) - .isA(DateOffsetStrategy.class) - .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) - .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); + .isA(DateOffsetStrategy.class) + .then(s -> match((DateOffsetStrategy)s) + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offsetUnitValue || 0 == dateOffsetStrategy.offset)) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 89e437eb..7e13a313 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -42,10 +42,10 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni String name = CriteriaNameHelper.getCriteriaName(criteria.criteria) + " at " + groupName; Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) - .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) - || (c.startWindow.end != null && c.startWindow.end.days != null)) - || c.startWindow != null && (( c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) - || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) + .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) + || (c.startWindow.end != null && c.startWindow.end.days != null)) + || c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) + || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java index aa307330..dafce095 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java @@ -18,12 +18,14 @@ */ package org.ohdsi.circe.cohortdefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author Chris Knoll */ +@JsonIgnoreProperties(ignoreUnknown = true) public class CustomEraStrategy extends EndStrategy { @JsonProperty("DrugCodesetId") @@ -32,17 +34,19 @@ public class CustomEraStrategy extends EndStrategy { @JsonProperty("GapDays") public int gapDays = 0; @JsonProperty("GapUnit") - public String gapUnit = "day"; + public String gapUnit = IntervalUnit.DAY.getName();; @JsonProperty("GapUnitValue") - public int gapUnitValue = 0; + public Integer gapUnitValue = null; @JsonProperty("Offset") public int offset = 0; @JsonProperty("OffsetUnit") - public String offsetUnit = "day"; + public String offsetUnit = IntervalUnit.DAY.getName();; @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; + + @JsonProperty("DaysSupplyOverride") public Integer daysSupplyOverride = null; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 476f2bce..7ca8028c 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -27,22 +27,24 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class DateOffsetStrategy extends EndStrategy { - @JsonProperty("Offset") - public int offset = 0; + public enum DateField { + StartDate, + EndDate + } + @JsonProperty("DateField") public DateField dateField = DateField.StartDate; + + @JsonProperty("Offset") + public int offset = 0; + + @JsonProperty("OffsetUnit") + public String offsetUnit = IntervalUnit.DAY.getName();; @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; - @JsonProperty("OffsetUnit") - public String offsetUnit = "day"; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { return dispatcher.getStrategySql(this, eventTable); } - - public enum DateField { - StartDate, - EndDate - } } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index 5667d448..091998e8 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -27,25 +27,30 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class Window { - @JsonProperty("Start") - public Endpoint start; - @JsonProperty("End") - public Endpoint end; - @JsonProperty("UseIndexEnd") - public Boolean useIndexEnd; - @JsonProperty("UseEventEnd") - public Boolean useEventEnd; - + @JsonIgnoreProperties(ignoreUnknown = true) public static class Endpoint { - @JsonProperty("Days") public Integer days; + + @JsonProperty("Coeff") + public int coeff; @JsonProperty("TimeUnitValue") public Integer timeUnitValue; + @JsonProperty("TimeUnit") - public String timeUnit = "day"; + public String timeUnit = IntervalUnit.DAY.getName(); + } + + @JsonProperty("Start") + public Endpoint start; - @JsonProperty("Coeff") - public int coeff; - } + @JsonProperty("End") + public Endpoint end; + + @JsonProperty("UseIndexEnd") + public Boolean useIndexEnd; + + @JsonProperty("UseEventEnd") + public Boolean useEventEnd; + } diff --git a/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar b/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..92c04a2c63ec1ba3d1919cf086c50f2c80141970 GIT binary patch literal 57903 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�-i4M0y@TeY92;~G0l`m8) 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 j8xQk;-LISczWG1#jxKrv!o% op_end_date then op_end_date - else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date +select event_id, person_id, + case when DATEADD(@offsetUnit,@offsetUnitValue,@dateField) > op_end_date then op_end_date else DATEADD(@offsetUnit,@offsetUnitValue,@dateField) end as end_date INTO #strategy_ends from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index b2d1145b..b8153623 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -62,8 +62,8 @@ from ( --cteEnds FROM #cohort_rows c JOIN ( -- cteEndDates SELECT - person_id, - DATEADD(@era_pad_unit, -1 * @eraconstructorpad, event_date) as end_date + person_id + , DATEADD(@eraPadUnit,-1 * @eraPadValue, event_date) as end_date FROM ( SELECT @@ -83,7 +83,7 @@ from ( --cteEnds SELECT person_id - , DATEADD(@era_pad_unit, -1 * @eraconstructorpad, end_date) as end_date + , DATEADD(@eraPadUnit,@eraPadValue,end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 4ddeb5e8..52a7f84f 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -137,7 +137,57 @@ public void compareTo() { assertEquals(5, Comparisons.compareTo(filter, window)); } - + @Test + public void compareToHours(){ + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.timeUnit = "hour"; + window.start.timeUnitValue = 72; + window.start.coeff = -1; + + window.end.timeUnit = "hour"; + window.end.timeUnitValue = 120; + window.end.coeff = 1; + assertEquals(604800, Comparisons.compareTo(filter, window)); + } + @Test + public void compareToMinute() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.timeUnit = "minute"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.timeUnit = "minute"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + assertEquals(1289700, Comparisons.compareTo(filter, window)); + } + @Test + public void compareToSecond() { + ObservationFilter filter = new ObservationFilter(); + filter.postDays = 10; + filter.priorDays = 5; + Window window = new Window(); + window.start = new Window.Endpoint(); + window.end = new Window.Endpoint(); + window.start.timeUnit = "second"; + window.start.timeUnitValue = 30; + window.start.coeff = -1; + + window.end.timeUnit = "second"; + window.end.timeUnitValue = 75; + window.end.coeff = 1; + assertEquals(1295895, Comparisons.compareTo(filter, window)); + } @Test public void compareToHours() { ObservationFilter filter = new ObservationFilter(); diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java index fe42e08f..1171819b 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java @@ -31,9 +31,9 @@ public void checkCorrect() { List warnings = check.check(CORRECT_EXPRESSION); assertEquals(0, warnings.size()); } - @Test - public void checkCorrectSecond() { - List warnings = check.check(CORRECT_EXPRESSION_SECOND); - assertEquals(0, warnings.size()); - } + @Test + public void checkCorrectSecond() { + List warnings = check.check(CORRECT_EXPRESSION_SECOND); + assertEquals(0, warnings.size()); + } } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index 49ea1f26..86e6423d 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -485,7 +485,7 @@ public void customEraExitTestSecond() { )); } - + @Test public void conceptSetSimpleTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/conceptSet_simple.json")); @@ -671,7 +671,7 @@ public void nullConceptSetListTest() { pf.renderConceptSetList((ConceptSet[])null); } - + @Test public void dateAdjustTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/dateAdjust.json")); @@ -690,9 +690,9 @@ public void dateAdjustTest() { "12. visit occurrences of 'Concept Set 1', starting 10 days after and ending 20 days after the event start date.", "13. visit details of 'Concept Set 1', starting 10 days after and ending 20 days after the event start date." )); - + } - + @Test public void emptyConceptListTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/emptyConceptList.json")); @@ -700,6 +700,6 @@ public void emptyConceptListTest() { assertThat(markdown, stringContainsInOrder( "1. condition occurrences of 'Concept Set 1', a provider specialty that is: [none specified]; a visit occurrence that is: [none specified]." )); - + } } From 5e03963dcdc2626e7fafc622bffa392946cc83db Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Tue, 9 Jan 2024 19:18:05 +0700 Subject: [PATCH 14/25] update test --- .../CohortExpressionQueryBuilder.java | 86 +++++++++++-------- .../cohortdefinition/CollapseSettings.java | 19 ++-- .../ConditionOccurrenceSqlBuilder.java | 17 ++-- .../circe/check/checkers/ComparisonsTest.java | 52 +---------- .../builders/WindowCriteria_5_0_0_Test.java | 17 ---- .../censorWindow/censorWindowExpression.json | 6 +- .../eraDupes/eraDupesExpression.json | 12 +-- .../eraDupes/eraDupes_PREP.json | 15 ++-- .../eraDupes/eraDupes_VERIFY.json | 2 +- .../exits/continuousExposureExpression.json | 9 +- src/test/resources/ddl/cdm_v5.0.sql | 2 +- src/test/resources/ddl/cdm_v5.2.sql | 2 +- src/test/resources/ddl/cdm_v5.3.sql | 2 +- src/test/resources/printfriendly/doseEra.json | 25 +----- .../resources/printfriendly/drugExposure.json | 13 +-- 15 files changed, 94 insertions(+), 185 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 883b440a..b34dcf21 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -20,8 +20,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; - -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang3.StringUtils; @@ -562,13 +563,13 @@ public String getWindowedCriteriaQuery(String sqlTemplate, WindowedCriteria crit clauses.add(String.format("%s >= %s", startEventDateExpression, startExpression)); } - if (startWindow.end.days != null) { - endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); + if (startWindow.end.days != null && (startWindow.end.timeUnit == null || IntervalUnit.DAY.getName().equals(startWindow.end.timeUnit))) { + endExpression = String.format("DATEADD(day,%d,%s)", startWindow.end.coeff * startWindow.end.days, startIndexDateExpression); + }else if(startWindow.end.timeUnitValue != null){ + endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.end.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); } - else if (startWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", startWindow.start.timeUnit, startWindow.end.coeff * startWindow.end.timeUnitValue, startIndexDateExpression); - } else { - endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + else { + endExpression = checkObservationPeriod ? (startWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; } if (endExpression != null) { @@ -582,29 +583,32 @@ else if (startWindow.end.timeUnitValue != null) { String endIndexDateExpression = (endWindow.useIndexEnd != null && endWindow.useIndexEnd) ? "P.END_DATE" : "P.START_DATE"; // for backwards compatability, having a null endWindow.useIndexEnd means they SHOULD use the index end date. String endEventDateExpression = (endWindow.useEventEnd == null || endWindow.useEventEnd) ? "A.END_DATE" : "A.START_DATE"; - if (endWindow.start.days != null) { - startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); - } else if (endWindow.start.timeUnitValue != null) { - startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); - } else { - startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; - } - - if (startExpression != null) { - clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); - } + if (endWindow.start.days != null && (endWindow.start.timeUnit == null || IntervalUnit.DAY.getName().equals(endWindow.start.timeUnit))) { + startExpression = String.format("DATEADD(day,%d,%s)", endWindow.start.coeff * endWindow.start.days, endIndexDateExpression); + }else if(endWindow.start.timeUnitValue != null){ + startExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.start.coeff * endWindow.start.timeUnitValue, endIndexDateExpression); + } + else { + startExpression = checkObservationPeriod ? (endWindow.start.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + } - if (endWindow.end.days != null) { - endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); - } else if (endWindow.end.timeUnitValue != null) { - endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.start.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); - } else { - endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; - } + if (startExpression != null) { + clauses.add(String.format("%s >= %s", endEventDateExpression, startExpression)); + } - if (endExpression != null) { - clauses.add(String.format("%s <= %s", endEventDateExpression, endExpression)); - } + if (endWindow.end.days != null && (endWindow.end.timeUnit == null || IntervalUnit.DAY.getName().equals(endWindow.end.timeUnit))) { + endExpression = String.format("DATEADD(day,%d,%s)", endWindow.end.coeff * endWindow.end.days, endIndexDateExpression); + } + else if(endWindow.end.timeUnitValue != null){ + endExpression = String.format("DATEADD(%s,%d,%s)", endWindow.end.timeUnit, endWindow.end.coeff * endWindow.end.timeUnitValue, endIndexDateExpression); + } + else { + endExpression = checkObservationPeriod ? (endWindow.end.coeff == -1 ? "P.OP_START_DATE" : "P.OP_END_DATE") : null; + } + + if (endExpression != null) { + clauses.add(String.format("%s <= %s", endEventDateExpression, endExpression)); + } } // RestrictVisit @@ -776,7 +780,7 @@ private String getDateFieldForOffsetStrategy(DateOffsetStrategy.DateField dateFi @Override public String getStrategySql(DateOffsetStrategy strat, String eventTable) { String strategySql = StringUtils.replace(DATE_OFFSET_STRATEGY_TEMPLATE, "@eventTable", eventTable); - if (strat.offsetUnit == null || strat.offsetUnit.equals(IntervalUnit.DAY.getName())) { + if (strat.offsetUnit == null || IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); } else { @@ -796,23 +800,33 @@ public String getStrategySql(CustomEraStrategy strat, String eventTable) { } String drugExposureEndDateExpression = DEFAULT_DRUG_EXPOSURE_END_DATE_EXPRESSION; - if (strat.daysSupplyOverride != null) { + if (strat.daysSupplyOverride != null && IntervalUnit.DAY.getName().equals(strat.gapUnit)) { drugExposureEndDateExpression = String.format("DATEADD(day,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); + }else if(strat.daysSupplyOverride != null && IntervalUnit.HOUR.getName().equals(strat.gapUnit)){ + drugExposureEndDateExpression = String.format("DATEADD(hour,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); + }else if(strat.daysSupplyOverride != null && IntervalUnit.MINUTE.getName().equals(strat.gapUnit)){ + drugExposureEndDateExpression = String.format("DATEADD(minute,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); + }else if(strat.daysSupplyOverride != null && IntervalUnit.SECOND.getName().equals(strat.gapUnit)){ + drugExposureEndDateExpression = String.format("DATEADD(second,%d,DRUG_EXPOSURE_START_DATE)", strat.daysSupplyOverride); } String strategySql = StringUtils.replace(CUSTOM_ERA_STRATEGY_TEMPLATE, "@eventTable", eventTable); strategySql = StringUtils.replace(strategySql, "@drugCodesetId", strat.drugCodesetId.toString()); if (IntervalUnit.DAY.getName().equals(strat.gapUnit)) { strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapDays)); - } else { + strategySql = StringUtils.replace(strategySql, "@gapUnit", "day"); + }else { strategySql = StringUtils.replace(strategySql, "@gapUnitValue", Integer.toString(strat.gapUnitValue)); + strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); + } - strategySql = StringUtils.replace(strategySql, "@gapUnit", strat.gapUnit); - if (IntervalUnit.DAY.getName().equals(strat.offsetUnit)) { + if(IntervalUnit.DAY.getName().equals(strat.offsetUnit) || strat.offsetUnit == null){ strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offset)); - } else { + strategySql = StringUtils.replace(strategySql, "@offsetUnit", "day"); + }else { strategySql = StringUtils.replace(strategySql, "@offsetUnitValue", Integer.toString(strat.offsetUnitValue)); + strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); } - strategySql = StringUtils.replace(strategySql, "@offsetUnit", strat.offsetUnit); + strategySql = StringUtils.replace(strategySql, "@drugExposureEndDateExpression", drugExposureEndDateExpression); return strategySql; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 2fcfc1f5..1c2140fd 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -23,14 +23,17 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class CollapseSettings { + + @JsonProperty("CollapseType") + public CollapseType collapseType = CollapseType.ERA; + + @JsonProperty("EraPad") + public int eraPad = 0; - @JsonProperty("CollapseType") - public CollapseType collapseType = CollapseType.ERA; - @JsonProperty("EraPad") - public int eraPad = 0; - @JsonProperty("EraPadUnit") - public String eraPadUnit = IntervalUnit.DAY.getName(); - @JsonProperty("EraPadUnitValue") - public int eraPadUnitValue = 0; + @JsonProperty("EraPadUnit") + public String eraPadUnit = IntervalUnit.DAY.getName(); + + @JsonProperty("EraPadValue") + public int eraPadUnitValue = 0; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 1a7b1b61..d032505b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -65,7 +65,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w // first if (criteria.first != null && criteria.first == true) { whereClauses.add("C.ordinal = 1"); - query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_datetime, co.condition_occurrence_id) as ordinal"); + query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY co.person_id ORDER BY co.condition_start_date, co.condition_occurrence_id) as ordinal"); } else { query = StringUtils.replace(query, "@ordinalExpression", ""); } @@ -93,14 +93,13 @@ protected List resolveSelectClauses(T criteria) { selectCols.add("co.condition_status_concept_id"); } // dateAdjustment or default start/end dates - if (criteria.dateAdjustment != null) { - selectCols.add( - BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, - criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))", - criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_datetime" : "COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime))")); - } else { - selectCols.add("co.condition_start_datetime as start_date, COALESCE(co.condition_end_datetime, DATEADD(day,1,co.condition_start_datetime)) as end_date"); - } + if (criteria.dateAdjustment != null) { + selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, + criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", + criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); + } else { + selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + } return selectCols; } diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java index 52a7f84f..4ddeb5e8 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -137,57 +137,7 @@ public void compareTo() { assertEquals(5, Comparisons.compareTo(filter, window)); } - @Test - public void compareToHours(){ - ObservationFilter filter = new ObservationFilter(); - filter.postDays = 10; - filter.priorDays = 5; - Window window = new Window(); - window.start = new Window.Endpoint(); - window.end = new Window.Endpoint(); - window.start.timeUnit = "hour"; - window.start.timeUnitValue = 72; - window.start.coeff = -1; - - window.end.timeUnit = "hour"; - window.end.timeUnitValue = 120; - window.end.coeff = 1; - assertEquals(604800, Comparisons.compareTo(filter, window)); - } - @Test - public void compareToMinute() { - ObservationFilter filter = new ObservationFilter(); - filter.postDays = 10; - filter.priorDays = 5; - Window window = new Window(); - window.start = new Window.Endpoint(); - window.end = new Window.Endpoint(); - window.start.timeUnit = "minute"; - window.start.timeUnitValue = 30; - window.start.coeff = -1; - - window.end.timeUnit = "minute"; - window.end.timeUnitValue = 75; - window.end.coeff = 1; - assertEquals(1289700, Comparisons.compareTo(filter, window)); - } - @Test - public void compareToSecond() { - ObservationFilter filter = new ObservationFilter(); - filter.postDays = 10; - filter.priorDays = 5; - Window window = new Window(); - window.start = new Window.Endpoint(); - window.end = new Window.Endpoint(); - window.start.timeUnit = "second"; - window.start.timeUnitValue = 30; - window.start.coeff = -1; - - window.end.timeUnit = "second"; - window.end.timeUnitValue = 75; - window.end.coeff = 1; - assertEquals(1295895, Comparisons.compareTo(filter, window)); - } + @Test public void compareToHours() { ObservationFilter filter = new ObservationFilter(); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java index fc863525..5ab6066e 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java @@ -175,23 +175,6 @@ public void windowDoseEraTest() throws Exception { this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); } - - @Test - public void windowDoseEraTestHour() throws Exception { - - final String resultsSchema = "window_dose_era"; - final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", - "/windowcriteria/windowDoseEra_PREP.json"}; - final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowDoseEra_VERIFY.json"}; - WindowedCriteria wc = new WindowedCriteria(); - wc.criteria = new DoseEra(); // find any dose era - wc.startWindow = CriteriaUtils.getPriorHoursWindow(); - List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, - CriteriaColumn.VALUE_AS_NUMBER, CriteriaColumn.UNIT, CriteriaColumn.DURATION); - this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); - - } - @Test public void windowDrugEraTest() throws Exception { diff --git a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json index 5a8056ea..d7c071b6 100644 --- a/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json +++ b/src/test/resources/cohortgeneration/censorWindow/censorWindowExpression.json @@ -50,15 +50,13 @@ "EndStrategy": { "DateOffset": { "DateField": "StartDate", - "OffsetUnitValue": 90, - "OffsetUnit": "day" + "Offset": 90 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnitValue": 0, - "EraPadUnit": "day" + "EraPad": 0 }, "CensorWindow": { "StartDate": "2000-04-01", diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json index 543fa6d7..0d27bab9 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupesExpression.json @@ -2,8 +2,8 @@ "ConceptSets": [], "PrimaryCriteria": { "CriteriaList": [{ - "ConditionOccurrence": { - } + "ConditionOccurrence": { + } }], "ObservationWindow": { "PriorDays": 0, @@ -23,17 +23,13 @@ "EndStrategy": { "DateOffset": { "DateField": "EndDate", - "Offset": 0, - "OffsetUnit": "hour", - "OffsetUnitValue": 0 + "Offset": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 43200, - "EraPadUnit": "minute", - "EraPadUnitValue": 0 + "EraPad": 30 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json index 806f6510..322a0f0c 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json @@ -14,9 +14,8 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-06-16", - "condition_start_datetime":"2009-06-16 00:00:00", "condition_end_date":"2009-06-19", - "condition_end_datetime":"2009-06-19 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -24,9 +23,8 @@ "person_id":1, "condition_concept_id":2, "condition_start_date":"2009-07-01", - "condition_start_datetime":"2009-07-01 00:00:00", "condition_end_date":"2009-07-04", - "condition_end_datetime":"2009-07-04 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -34,9 +32,8 @@ "person_id":1, "condition_concept_id":3, "condition_start_date":"2009-07-27", - "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", - "condition_end_datetime":"2009-07-30 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -44,9 +41,8 @@ "person_id":1, "condition_concept_id":4, "condition_start_date":"2009-07-27", - "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", - "condition_end_datetime":"2009-07-30 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -54,9 +50,8 @@ "person_id":1, "condition_concept_id":5, "condition_start_date":"2009-07-27", - "condition_start_datetime":"2009-07-27 00:00:00", "condition_end_date":"2009-07-30", - "condition_end_datetime":"2009-07-30 00:00:00", + "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 } ], diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json index 20615fa7..f62ad136 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_VERIFY.json @@ -4,7 +4,7 @@ "cohort_definition_id":1, "subject_id":1, "cohort_start_date":"2009-06-16", - "cohort_end_date":"2009-06-27" + "cohort_end_date":"2009-07-30" } ] } \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json index a064c47e..5be887e2 100644 --- a/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json +++ b/src/test/resources/cohortgeneration/exits/continuousExposureExpression.json @@ -50,17 +50,14 @@ "EndStrategy": { "CustomEra": { "DrugCodesetId": 0, - "GapUnit": "day", - "GapUnitValue": 60, - "OffsetUnit": "day", - "OffsetUnitValue": 0 + "GapDays": 60, + "Offset": 0 } }, "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {} } \ No newline at end of file diff --git a/src/test/resources/ddl/cdm_v5.0.sql b/src/test/resources/ddl/cdm_v5.0.sql index e4771e5f..eb0cb155 100644 --- a/src/test/resources/ddl/cdm_v5.0.sql +++ b/src/test/resources/ddl/cdm_v5.0.sql @@ -286,7 +286,7 @@ CREATE TABLE @schemaName.condition_occurrence person_id INTEGER NOT NULL , condition_concept_id INTEGER NOT NULL , condition_start_date DATE NOT NULL , - condition_start_datetime TIMESTAMP NOT NULL , + condition_start_datetime TIMESTAMP NULL , condition_end_date DATE NULL , condition_end_datetime TIMESTAMP NULL , condition_type_concept_id INTEGER NOT NULL , diff --git a/src/test/resources/ddl/cdm_v5.2.sql b/src/test/resources/ddl/cdm_v5.2.sql index cc1c9686..55773d3c 100644 --- a/src/test/resources/ddl/cdm_v5.2.sql +++ b/src/test/resources/ddl/cdm_v5.2.sql @@ -292,7 +292,7 @@ CREATE TABLE @schemaName.condition_occurrence person_id INTEGER NOT NULL , condition_concept_id INTEGER NOT NULL , condition_start_date DATE NOT NULL , - condition_start_datetime TIMESTAMP NOT NULL , + condition_start_datetime TIMESTAMP NULL , condition_end_date DATE NULL , condition_end_datetime TIMESTAMP NULL , condition_type_concept_id INTEGER NOT NULL , diff --git a/src/test/resources/ddl/cdm_v5.3.sql b/src/test/resources/ddl/cdm_v5.3.sql index 456ad4b8..d4975350 100644 --- a/src/test/resources/ddl/cdm_v5.3.sql +++ b/src/test/resources/ddl/cdm_v5.3.sql @@ -315,7 +315,7 @@ CREATE TABLE @schemaName.condition_occurrence person_id INTEGER NOT NULL , condition_concept_id INTEGER NOT NULL , condition_start_date DATE NOT NULL , - condition_start_datetime TIMESTAMP NOT NULL , + condition_start_datetime TIMESTAMP NULL , condition_end_date DATE NULL , condition_end_datetime TIMESTAMP NULL , condition_type_concept_id INTEGER NOT NULL , diff --git a/src/test/resources/printfriendly/doseEra.json b/src/test/resources/printfriendly/doseEra.json index 7fb29479..ccbc833e 100644 --- a/src/test/resources/printfriendly/doseEra.json +++ b/src/test/resources/printfriendly/doseEra.json @@ -39,14 +39,10 @@ "StartWindow": { "Start": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -65,13 +61,10 @@ "StartWindow": { "Start": { "Days": 30, - "TimeUnitValue": 30, - "TimeUnit": "day", "Coeff": -1 }, "End": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "UseEventEnd": false @@ -176,14 +169,10 @@ "StartWindow": { "Start": { "Days": 60, - "TimeUnitValue": 60, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -202,14 +191,10 @@ "StartWindow": { "Start": { "Days": 60, - "TimeUnitValue": 60, - "TimeUnit": "day", "Coeff": -1 }, "End": { "Days": 1, - "TimeUnitValue": 1, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -248,8 +233,6 @@ }, "End": { "Days": 0, - "TimeUnitValue": 0, - "TimeUnit": "day", "Coeff": 1 }, "UseEventEnd": false @@ -275,8 +258,6 @@ }, "End": { "Days": 1, - "TimeUnitValue": 1, - "TimeUnit": "day", "Coeff": -1 }, "UseEventEnd": false @@ -294,10 +275,8 @@ ], "CensoringCriteria": [], "CollapseSettings": { - "EraPad": 0, "CollapseType": "ERA", - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" diff --git a/src/test/resources/printfriendly/drugExposure.json b/src/test/resources/printfriendly/drugExposure.json index 7caadd91..fb50b1e2 100644 --- a/src/test/resources/printfriendly/drugExposure.json +++ b/src/test/resources/printfriendly/drugExposure.json @@ -40,8 +40,7 @@ "Coeff": -1 }, "End": { - "TimeUnitValue": 1, - "TimeUnit": "day", + "Days": 1, "Coeff": -1 }, "UseEventEnd": false @@ -59,13 +58,11 @@ }, "StartWindow": { "Start": { - "TimeUnitValue": 14, - "TimeUnit": "day", + "Days": 14, "Coeff": -1 }, "End": { - "TimeUnitValue": 0, - "TimeUnit": "day", + "Days": 0, "Coeff": -1 }, "UseEventEnd": false @@ -250,9 +247,7 @@ "CensoringCriteria": [], "CollapseSettings": { "CollapseType": "ERA", - "EraPad": 0, - "EraPadUnit": "day", - "EraPadUnitValue": 0 + "EraPad": 0 }, "CensorWindow": {}, "cdmVersionRange": ">=5.0.0" From de9527c2d241c9a06312e434ae3b36a04230e6ad Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Tue, 9 Jan 2024 08:30:12 -0600 Subject: [PATCH 15/25] fix indent on editor --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index d6d2a1b6..11a58414 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,4 @@ root = true [*.{java,sql}] charset = utf-8 indent_style = space -indent_size = 4 +indent_size = 2 From ca6bbfde06bb542ab490f773bd70ea1994483d0e Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Mon, 15 Jan 2024 14:37:36 -0600 Subject: [PATCH 16/25] remove jar --- .../resources/SqlRender-1.15.0-SNAPSHOT.jar | Bin 57903 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar diff --git a/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar b/src/main/resources/resources/SqlRender-1.15.0-SNAPSHOT.jar deleted file mode 100644 index 92c04a2c63ec1ba3d1919cf086c50f2c80141970..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57903 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�-i4M0y@TeY92;~G0l`m8) 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 j8xQk;-LISczWG1#jxKrv!o% Date: Tue, 16 Jan 2024 19:20:45 +0700 Subject: [PATCH 17/25] Revert "fix" This reverts commit f819d5e6d6f880cf558274d124e152ec38f73954. --- .../checkers/ExitCriteriaDaysOffsetCheck.java | 10 +++--- .../checkers/FirstTimeInHistoryCheck.java | 8 ++--- .../cohortdefinition/CustomEraStrategy.java | 10 ++---- .../cohortdefinition/DateOffsetStrategy.java | 20 +++++------ .../ohdsi/circe/cohortdefinition/Window.java | 33 ++++++++----------- .../sql/dateOffsetStrategy.sql | 7 ++-- .../cohortdefinition/sql/generateCohort.sql | 6 ++-- .../checkers/DeathTimeWindowCheckTest.java | 10 +++--- .../printfriendly/PrintFriendlyTest.java | 10 +++--- 9 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java index 1ee188c1..e4294203 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ExitCriteriaDaysOffsetCheck.java @@ -22,6 +22,8 @@ import org.ohdsi.circe.cohortdefinition.CohortExpression; import org.ohdsi.circe.cohortdefinition.DateOffsetStrategy; +import java.util.Arrays; +import java.util.List; import java.util.Objects; import static org.ohdsi.circe.check.operations.Operations.match; @@ -41,9 +43,9 @@ protected WarningSeverity defineSeverity() { protected void check(CohortExpression expression, WarningReporter reporter) { match(expression.endStrategy) - .isA(DateOffsetStrategy.class) - .then(s -> match((DateOffsetStrategy)s) - .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && (0 == dateOffsetStrategy.offsetUnitValue || 0 == dateOffsetStrategy.offset)) - .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); + .isA(DateOffsetStrategy.class) + .then(s -> match((DateOffsetStrategy)s) + .when(dateOffsetStrategy -> Objects.equals(StartDate, dateOffsetStrategy.dateField) && 0 == dateOffsetStrategy.offsetUnitValue) + .then(dateOffsetStrategy -> reporter.add(String.format(DAYS_OFFSET_WARNING, dateOffsetStrategy.offsetUnit)))); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java index 7e13a313..89e437eb 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -42,10 +42,10 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni String name = CriteriaNameHelper.getCriteriaName(criteria.criteria) + " at " + groupName; Execution addWarning = () -> reporter.add(WARNING, name); match(criteria) - .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) - || (c.startWindow.end != null && c.startWindow.end.days != null)) - || c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) - || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) + .when(c -> c.startWindow != null && ((c.startWindow.start != null && c.startWindow.start.days != null) + || (c.startWindow.end != null && c.startWindow.end.days != null)) + || c.startWindow != null && (( c.startWindow.start != null && c.startWindow.start.timeUnitValue != null) + || (c.startWindow.end != null) && c.startWindow.end.timeUnitValue != null)) .then(cc -> match(cc.criteria) .isA(ConditionEra.class) .then(c -> match((ConditionEra)c) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java index dafce095..aa307330 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CustomEraStrategy.java @@ -18,14 +18,12 @@ */ package org.ohdsi.circe.cohortdefinition; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * * @author Chris Knoll */ -@JsonIgnoreProperties(ignoreUnknown = true) public class CustomEraStrategy extends EndStrategy { @JsonProperty("DrugCodesetId") @@ -34,19 +32,17 @@ public class CustomEraStrategy extends EndStrategy { @JsonProperty("GapDays") public int gapDays = 0; @JsonProperty("GapUnit") - public String gapUnit = IntervalUnit.DAY.getName();; + public String gapUnit = "day"; @JsonProperty("GapUnitValue") - public Integer gapUnitValue = null; + public int gapUnitValue = 0; @JsonProperty("Offset") public int offset = 0; @JsonProperty("OffsetUnit") - public String offsetUnit = IntervalUnit.DAY.getName();; + public String offsetUnit = "day"; @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; - - @JsonProperty("DaysSupplyOverride") public Integer daysSupplyOverride = null; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java index 7ca8028c..476f2bce 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/DateOffsetStrategy.java @@ -27,24 +27,22 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class DateOffsetStrategy extends EndStrategy { - public enum DateField { - StartDate, - EndDate - } - - @JsonProperty("DateField") - public DateField dateField = DateField.StartDate; - @JsonProperty("Offset") public int offset = 0; - - @JsonProperty("OffsetUnit") - public String offsetUnit = IntervalUnit.DAY.getName();; + @JsonProperty("DateField") + public DateField dateField = DateField.StartDate; @JsonProperty("OffsetUnitValue") public int offsetUnitValue = 0; + @JsonProperty("OffsetUnit") + public String offsetUnit = "day"; @Override public String accept(IGetEndStrategySqlDispatcher dispatcher, String eventTable) { return dispatcher.getStrategySql(this, eventTable); } + + public enum DateField { + StartDate, + EndDate + } } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java index 091998e8..5667d448 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Window.java @@ -27,30 +27,25 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class Window { - @JsonIgnoreProperties(ignoreUnknown = true) + @JsonProperty("Start") + public Endpoint start; + @JsonProperty("End") + public Endpoint end; + @JsonProperty("UseIndexEnd") + public Boolean useIndexEnd; + @JsonProperty("UseEventEnd") + public Boolean useEventEnd; + public static class Endpoint { + @JsonProperty("Days") public Integer days; - - @JsonProperty("Coeff") - public int coeff; @JsonProperty("TimeUnitValue") public Integer timeUnitValue; - @JsonProperty("TimeUnit") - public String timeUnit = IntervalUnit.DAY.getName(); - } - - @JsonProperty("Start") - public Endpoint start; - - @JsonProperty("End") - public Endpoint end; + public String timeUnit = "day"; - @JsonProperty("UseIndexEnd") - public Boolean useIndexEnd; - - @JsonProperty("UseEventEnd") - public Boolean useEventEnd; - + @JsonProperty("Coeff") + public int coeff; + } } diff --git a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql index cbe82232..b79289dd 100644 --- a/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/dateOffsetStrategy.sql @@ -1,6 +1,9 @@ -- date offset strategy -select event_id, person_id, - case when DATEADD(@offsetUnit,@offsetUnitValue,@dateField) > op_end_date then op_end_date else DATEADD(@offsetUnit,@offsetUnitValue,@dateField) end as end_date +select event_id, + person_id, + case + when DATEADD(@offsetUnit, @offsetUnitValue, @dateField) > op_end_date then op_end_date + else DATEADD(@offsetUnit, @offsetUnitValue, @dateField) end as end_date INTO #strategy_ends from @eventTable; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index b8153623..b2d1145b 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -62,8 +62,8 @@ from ( --cteEnds FROM #cohort_rows c JOIN ( -- cteEndDates SELECT - person_id - , DATEADD(@eraPadUnit,-1 * @eraPadValue, event_date) as end_date + person_id, + DATEADD(@era_pad_unit, -1 * @eraconstructorpad, event_date) as end_date FROM ( SELECT @@ -83,7 +83,7 @@ from ( --cteEnds SELECT person_id - , DATEADD(@eraPadUnit,@eraPadValue,end_date) as end_date + , DATEADD(@era_pad_unit, -1 * @eraconstructorpad, end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java index 1171819b..fe42e08f 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DeathTimeWindowCheckTest.java @@ -31,9 +31,9 @@ public void checkCorrect() { List warnings = check.check(CORRECT_EXPRESSION); assertEquals(0, warnings.size()); } - @Test - public void checkCorrectSecond() { - List warnings = check.check(CORRECT_EXPRESSION_SECOND); - assertEquals(0, warnings.size()); - } + @Test + public void checkCorrectSecond() { + List warnings = check.check(CORRECT_EXPRESSION_SECOND); + assertEquals(0, warnings.size()); + } } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java index 86e6423d..49ea1f26 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -485,7 +485,7 @@ public void customEraExitTestSecond() { )); } - + @Test public void conceptSetSimpleTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/conceptSet_simple.json")); @@ -671,7 +671,7 @@ public void nullConceptSetListTest() { pf.renderConceptSetList((ConceptSet[])null); } - + @Test public void dateAdjustTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/dateAdjust.json")); @@ -690,9 +690,9 @@ public void dateAdjustTest() { "12. visit occurrences of 'Concept Set 1', starting 10 days after and ending 20 days after the event start date.", "13. visit details of 'Concept Set 1', starting 10 days after and ending 20 days after the event start date." )); - + } - + @Test public void emptyConceptListTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/emptyConceptList.json")); @@ -700,6 +700,6 @@ public void emptyConceptListTest() { assertThat(markdown, stringContainsInOrder( "1. condition occurrences of 'Concept Set 1', a provider specialty that is: [none specified]; a visit occurrence that is: [none specified]." )); - + } } From 6fb7fd25e217b470aee91330bfcc88ab3a458f00 Mon Sep 17 00:00:00 2001 From: Juan Carlos Namendi Pineda Date: Wed, 17 Jan 2024 11:09:32 +0700 Subject: [PATCH 18/25] add tests --- .../cohortdefinition/CollapseSettings.java | 2 +- .../printfriendly/inputTypes.ftl | 48 +- .../cohortdefinition/sql/generateCohort.sql | 6 +- .../CohortGeneration_5_0_0_Test.java | 19 + .../builders/CriteriaUtils.java | 47 +- .../builders/WindowCriteria_5_0_0_Test.java | 46 + ...riteriaExpressionWithTimeUnitInterval.json | 2216 +++++++++++++++++ .../eraDupes/eraDupes_PREP.json | 5 - 8 files changed, 2333 insertions(+), 56 deletions(-) create mode 100644 src/test/resources/cohortgeneration/allCriteria/allCriteriaExpressionWithTimeUnitInterval.json diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java index 1c2140fd..8456b30a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CollapseSettings.java @@ -33,7 +33,7 @@ public class CollapseSettings { @JsonProperty("EraPadUnit") public String eraPadUnit = IntervalUnit.DAY.getName(); - @JsonProperty("EraPadValue") + @JsonProperty("EraPadUnitValue") public int eraPadUnitValue = 0; } diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 6914b569..03eb6486 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -25,8 +25,8 @@ END Note!!!! --><#if range.op?ends_with("bt")> and <#if range.extent?has_content>${utils.formatDate(range.extent)}<#else>_empty_ <#-- ConceptList --> -<#macro ConceptList list quote="\""><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if item?counter gt 1><#if -item?counter == list?size> or <#else>, ${item} +<#macro ConceptList list quote="\""><#if (list?size > 0)><#list list?map(item->(quote + item.conceptName?lower_case + quote)) as item><#if +item?counter gt 1><#if item?counter == list?size> or <#else>, ${item}<#else>[none specified] <#-- ConceptSetSelection --> <#macro ConceptSetSelection selection defaultName="any"><#if selection.isExcluded!false>not in ${utils.codesetName(selection.codesetId!"", defaultName)} @@ -89,40 +89,26 @@ item?counter == list?size> or <#else>, ${item} <#function whichIndexPart useEnd><#if useEnd><#return "end date"><#else><#return "start date"> <#function temporalDirection coeff><#if coeff lt 0><#return "before"><#else><#return "after"> -<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} -<#----> -<#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 > - anytime on or before ${indexLabel} - ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1> -<#if w.start.days??>in the ${w.start.days} days -<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}s<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} -<#----> -<#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit}s ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.end.timeUnit}s ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)} - +<#macro Window w indexLabel="cohort entry">${whichEventPart(w.useEventEnd!false)} <#-- +--><#if (w.start.timeUnit == "day" && w.end.timeUnit == "day") || (!w.start.timeUnit?? && !w.end.timeUnit??)><#-- +--><#if !w.start.days?? && w.end.days == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif (w.end.days!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.days??>in the ${w.start.days} days<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.start.days?? && (w.end.days!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.days} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.end.days?? && (w.start.days!0) gt 0 && w.end.coeff ==1> ${w.start.days} days ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else>between ${w.start.days!"all"} days ${temporalDirection(w.start.coeff)} and ${w.end.days!"all"} days ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else><#-- +--><#if !w.start.timeUnitValue?? && w.end.timeUnitValue == 0 && w.start.coeff == -1 >anytime on or before ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif (w.end.timeUnitValue!0) == 1 && w.start.coeff == -1 && w.end.coeff == -1><#if w.start.timeUnitValue??>in the ${w.start.timeUnitValue} ${w.start.timeUnit}<#else>anytime prior to ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.start.timeUnitValue?? && (w.end.timeUnitValue!0) gt 1 && w.start.coeff == -1>anytime up to ${w.end.timeUnitValue} ${w.start.timeUnit} ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#elseif !w.end.timeUnitValue?? && (w.start.timeUnitValue!0) gt 0 && w.end.coeff ==1> ${w.start.timeUnitValue} ${w.start.timeUnit} ${temporalDirection(w.start.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--><#else>between ${w.start.timeUnitValue!"all"} ${w.start.timeUnit} ${temporalDirection(w.start.coeff)} and ${w.end.timeUnitValue!"all"} ${w.start.timeUnit} ${temporalDirection(w.end.coeff)} ${indexLabel} ${whichIndexPart(w.useIndexEnd!false)}<#-- +--> <#-- User Defined Period --> <#macro UserDefinedPeriod p><#if p.startDate?has_content>a user defiend start date of ${utils.formatDate(p.startDate)}<#if p.endDate?has_content> and<#if -p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} - - +p.endDate?has_content><#if !p.startDate?has_content>a user defined end date of ${utils.formatDate(p.endDate)} <#-- Date Adjustment --> diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index b2d1145b..c49a855f 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -62,8 +62,8 @@ from ( --cteEnds FROM #cohort_rows c JOIN ( -- cteEndDates SELECT - person_id, - DATEADD(@era_pad_unit, -1 * @eraconstructorpad, event_date) as end_date + person_id + , DATEADD(@era_pad_unit, -1 * @eraconstructorpad, event_date) as end_date FROM ( SELECT @@ -83,7 +83,7 @@ from ( --cteEnds SELECT person_id - , DATEADD(@era_pad_unit, -1 * @eraconstructorpad, end_date) as end_date + , DATEADD(@era_pad_unit, @eraconstructorpad, end_date) as end_date , 1 AS event_type FROM #cohort_rows ) RAWDATA diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java index 6c437421..ea1fc3d6 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/CohortGeneration_5_0_0_Test.java @@ -136,6 +136,25 @@ public void allCriteriaTest() throws SQLException { jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); } + @Test + public void allCriteriaTestWithTimeUnitInterval() throws SQLException { + + final CohortExpressionQueryBuilder.BuildExpressionQueryOptions options = buildExpressionQueryOptions(1,"allCriteriaTest"); + + // prepare results schema + prepareSchema(options.resultSchema, RESULTS_DDL_PATH); + + // load 'all' criteria json + final CohortExpression expression = CohortExpression + .fromJson(ResourceHelper.GetResourceAsString("/cohortgeneration/allCriteria/allCriteriaExpressionWithTimeUnitInterval.json")); + + // build Sql + final String cohortSql = buildExpressionSql(expression, options); + + // execute on database, expect no errors + jdbcTemplate.batchUpdate(SqlSplit.splitSql(cohortSql)); + } + /* first occurrence tests */ private void setFirstOccurrenceCriteria(final Criteria c) throws Exception { final Field first = c.getClass().getDeclaredField("first"); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java index 37dd6a6a..1d09bd0c 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -15,6 +15,7 @@ */ package org.ohdsi.circe.cohortdefinition.builders; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.Occurrence; import org.ohdsi.circe.cohortdefinition.Window; @@ -41,23 +42,37 @@ public static Window getPrior365Window() { return prior365Window; } - public static Window getPriorHoursWindow() { - Window prior365Window = new Window(); - // index starts between 365d before - Window.Endpoint startPoint = new Window.Endpoint(); - startPoint.coeff = -1; - startPoint.days = null; - startPoint.timeUnitValue = 8760; - startPoint.timeUnit = "hour"; - prior365Window.start = startPoint; - // ... and 0 days before - Window.Endpoint endPoint = new Window.Endpoint(); - endPoint.coeff = -1; - endPoint.timeUnitValue = 0; - prior365Window.end = endPoint; - return prior365Window; + public static Window getPrior365WindowTimeUnitInterval(String timeUnit) { + Window prior365Window = new Window(); + // index starts between 365d before + Window.Endpoint startPoint = new Window.Endpoint(); + startPoint.coeff = -1; + startPoint.timeUnit = timeUnit; + startPoint.timeUnitValue = getTimeUnitValue(timeUnit, 365); + prior365Window.start = startPoint; + // ... and 0 days before + Window.Endpoint endPoint = new Window.Endpoint(); + endPoint.coeff = -1; + endPoint.days = getTimeUnitValue(timeUnit, 0); + prior365Window.end = endPoint; + return prior365Window; + } + + private static int getTimeUnitValue(String timeUnit, Integer timeUnitValueInDay) { + int timeUnitValue = timeUnitValueInDay; + if (IntervalUnit.HOUR.getName().equals(timeUnit)) { + timeUnitValue = timeUnitValueInDay * 24; } - + if (IntervalUnit.MINUTE.getName().equals(timeUnit)) { + timeUnitValue = timeUnitValueInDay * 24 * 60; + } + if (IntervalUnit.SECOND.getName().equals(timeUnit)) { + timeUnitValue = timeUnitValueInDay * 24 * 60 * 60; + } + return timeUnitValue; + } + + public static Window getAnyTimeWindow() { Window anytimeWindow = new Window(); // index starts between 365d before diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java index 5ab6066e..6cf34263 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_0_0_Test.java @@ -115,6 +115,22 @@ public void windowConditionEraTest() throws Exception { } + @Test + public void windowConditionEraTestInHourInterval() throws Exception { + + final String resultsSchema = "window_condition_era"; + final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", + "/windowcriteria/windowConditionEra_PREP.json"}; + final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowConditionEra_VERIFY.json"}; + WindowedCriteria wc = new WindowedCriteria(); + wc.criteria = new ConditionEra(); // find any condition era + wc.startWindow = CriteriaUtils.getPrior365WindowTimeUnitInterval(IntervalUnit.HOUR.getName()); + List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, + CriteriaColumn.ERA_OCCURRENCES, CriteriaColumn.DURATION); + this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); + + } + @Test public void windowConditionOccurrenceTest() throws Exception { @@ -130,6 +146,21 @@ public void windowConditionOccurrenceTest() throws Exception { } + @Test + public void windowConditionOccurrenceTestInMinuteInterval() throws Exception { + + final String resultsSchema = "window_condition_occurrence"; + final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", + "/windowcriteria/windowConditionOccurrence_PREP.json"}; + final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowConditionOccurrence_VERIFY.json"}; + WindowedCriteria wc = new WindowedCriteria(); + wc.criteria = new ConditionOccurrence(); // find any condition occurence + wc.startWindow = CriteriaUtils.getPrior365WindowTimeUnitInterval(IntervalUnit.MINUTE.getName()); + List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, CriteriaColumn.DURATION); + this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); + + } + @Test public void windowDeathTest() throws Exception { @@ -145,6 +176,21 @@ public void windowDeathTest() throws Exception { } + @Test + public void windowDeathTestInSecondInterval() throws Exception { + + final String resultsSchema = "window_death"; + final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", + "/windowcriteria/windowDeath_PREP.json"}; + final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowDeath_VERIFY.json"}; + WindowedCriteria wc = new WindowedCriteria(); + wc.criteria = new Death(); // find any death + wc.startWindow = CriteriaUtils.getPrior365WindowTimeUnitInterval(IntervalUnit.SECOND.getName()); + List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, CriteriaColumn.DURATION); + this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); + + } + @Test public void windowDeviceExposureTest() throws Exception { diff --git a/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpressionWithTimeUnitInterval.json b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpressionWithTimeUnitInterval.json new file mode 100644 index 00000000..f2d7ce01 --- /dev/null +++ b/src/test/resources/cohortgeneration/allCriteria/allCriteriaExpressionWithTimeUnitInterval.json @@ -0,0 +1,2216 @@ +{ + "ConceptSets": [ + { + "id": 0, + "name": "Empty Concept Set", + "expression": { + "items": [] + } + } + ], + "PrimaryCriteria": { + "CriteriaList": [ + { + "ObservationPeriod": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "First": true, + "PeriodEndDate": { + "Value": "2099-01-01", + "Extent": "2999-01-01", + "Op": "bt" + }, + "PeriodType": [ + { + "CONCEPT_CODE": "OMOP4822290", + "CONCEPT_ID": 44814725, + "CONCEPT_NAME": "Period inferred by algorithm", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Obs Period Type" + }, + { + "CONCEPT_CODE": "10021000000106", + "CONCEPT_ID": 44787739, + "CONCEPT_NAME": "FP34D drugs supplied payment form", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "UserDefinedPeriod": { + "StartDate": "1900-01-01", + "EndDate": "2060-01-01" + }, + "AgeAtStart": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "AgeAtEnd": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "PeriodLength": { + "Value": 0, + "Extent": 999, + "Op": "bt" + } + } + } + ], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [ + { + "name": "Demographic Test", + "description": "Demographic Test", + "expression": { + "Type": "ALL", + "CriteriaList": [], + "DemographicCriteriaList": [ + { + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "Race": [ + { + "CONCEPT_CODE": "2", + "CONCEPT_ID": 8515, + "CONCEPT_NAME": "Asian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + }, + { + "CONCEPT_CODE": "2.05", + "CONCEPT_ID": 38003578, + "CONCEPT_NAME": "Cambodian", + "DOMAIN_ID": "Race", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Race" + } + ], + "Ethnicity": [ + { + "CONCEPT_CODE": "Not Hispanic", + "CONCEPT_ID": 38003564, + "CONCEPT_NAME": "Not Hispanic or Latino", + "DOMAIN_ID": "Ethnicity", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Ethnicity" + } + ], + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + } + } + ], + "Groups": [] + } + }, + { + "name": "Condition Test", + "description": "Condition Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "ConditionType": [ + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "StopReason": { + "Text": "this is some test text", + "Op": "contains" + }, + "ConditionSourceConcept": 0, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "0396.00", + "CONCEPT_ID": 45444755, + "CONCEPT_NAME": "Psychologist", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Read" + }, + { + "CONCEPT_CODE": "03D5.00", + "CONCEPT_ID": 45418395, + "CONCEPT_NAME": "Consultant surgeon", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Read" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "IP", + "CONCEPT_ID": 9201, + "CONCEPT_NAME": "Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Condition Era Test", + "description": "Condition Era Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionEra": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "EraStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "EraEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceCount": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "EraLength": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "First": true, + "AgeAtStart": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "AgeAtEnd": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Death Test", + "description": "Death Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Death": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "DeathType": [ + { + "CONCEPT_CODE": "OMOP4822044", + "CONCEPT_ID": 256, + "CONCEPT_NAME": "EHR Record underlying cause of death", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822045", + "CONCEPT_ID": 255, + "CONCEPT_NAME": "EHR Record contributory cause of death", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822046", + "CONCEPT_ID": 254, + "CONCEPT_NAME": "EHR Record immediate cause of death", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Device Exposure Test", + "description": "Device Exposure Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DeviceExposure": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DeviceExposure": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "DeviceType": [ + { + "CONCEPT_CODE": "OMOP4822234", + "CONCEPT_ID": 32465, + "CONCEPT_NAME": "Inferred from claim", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Device Type" + }, + { + "CONCEPT_CODE": "OMOP4822236", + "CONCEPT_ID": 44818706, + "CONCEPT_NAME": "Patient reported device", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Device Type" + } + ], + "UniqueDeviceId": { + "Op": "contains" + }, + "Quantity": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "DeviceSourceConcept": 0, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "10", + "CONCEPT_ID": 38004455, + "CONCEPT_NAME": "Gastroenterology", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Medicare Specialty" + }, + { + "CONCEPT_CODE": "101YM0800X", + "CONCEPT_ID": 38003625, + "CONCEPT_NAME": "Mental Health Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + }, + { + "CONCEPT_CODE": "101YP1600X", + "CONCEPT_ID": 38003626, + "CONCEPT_NAME": "Pastoral Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "21", + "CONCEPT_ID": 8717, + "CONCEPT_NAME": "Inpatient Hospital", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "CMS Place of Service" + }, + { + "CONCEPT_CODE": "22", + "CONCEPT_ID": 8756, + "CONCEPT_NAME": "Outpatient Hospital", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "CMS Place of Service" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Drug Era Test", + "description": "Drug Era Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DrugEra": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DrugEra": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "EraStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "EraEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceCount": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "EraLength": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "First": true, + "AgeAtStart": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "AgeAtEnd": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Drug Exposure Test", + "description": "Drug Exposure Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DrugExposure": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "DrugExposure": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "DrugType": [ + { + "CONCEPT_CODE": "OMOP4822239", + "CONCEPT_ID": 38000175, + "CONCEPT_NAME": "Prescription dispensed in pharmacy", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Drug Type" + }, + { + "CONCEPT_CODE": "OMOP4822240", + "CONCEPT_ID": 38000176, + "CONCEPT_NAME": "Prescription dispensed through mail order", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Drug Type" + } + ], + "StopReason": { + "Text": "stop reason", + "Op": "contains" + }, + "Refills": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Quantity": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "DaysSupply": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "RouteConcept": [ + { + "CONCEPT_CODE": "6064005", + "CONCEPT_ID": 4263689, + "CONCEPT_NAME": "Topical route", + "DOMAIN_ID": "Route", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + }, + { + "CONCEPT_CODE": "10547007", + "CONCEPT_ID": 4023156, + "CONCEPT_NAME": "Auricular", + "DOMAIN_ID": "Route", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + }, + { + "CONCEPT_CODE": "12130007", + "CONCEPT_ID": 4006860, + "CONCEPT_NAME": "Intraarticular", + "DOMAIN_ID": "Route", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "EffectiveDrugDose": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "DoseUnit": [ + { + "CONCEPT_CODE": "%", + "CONCEPT_ID": 8554, + "CONCEPT_NAME": "percent", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + }, + { + "CONCEPT_CODE": "%(enzyme'activity}", + "CONCEPT_ID": 8849, + "CONCEPT_NAME": "percent enzyme activity", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + }, + { + "CONCEPT_CODE": "%[slope]", + "CONCEPT_ID": 9214, + "CONCEPT_NAME": "percent slope", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "LotNumber": { + "Text": "lot number", + "Op": "contains" + }, + "DrugSourceConcept": 0, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "1", + "CONCEPT_ID": 38004446, + "CONCEPT_NAME": "General Practice", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Medicare Specialty" + }, + { + "CONCEPT_CODE": "101YM0800X", + "CONCEPT_ID": 38003625, + "CONCEPT_NAME": "Mental Health Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "ERIP", + "CONCEPT_ID": 262, + "CONCEPT_NAME": "Emergency Room and Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + }, + { + "CONCEPT_CODE": "IP", + "CONCEPT_ID": 9201, + "CONCEPT_NAME": "Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Measurement Test", + "description": "Measurement Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Measurement": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "First": true, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "MeasurementType": [ + { + "CONCEPT_CODE": "OMOP4822059", + "CONCEPT_ID": 38000183, + "CONCEPT_NAME": "Inpatient detail - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "Operator": [ + { + "CONCEPT_CODE": "276140008", + "CONCEPT_ID": 4172704, + "CONCEPT_NAME": ">", + "DOMAIN_ID": "Meas Value Operator", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "ValueAsNumber": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "ValueAsConcept": [ + { + "CONCEPT_CODE": "10828004", + "CONCEPT_ID": 9191, + "CONCEPT_NAME": "Positive", + "DOMAIN_ID": "Meas Value", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Unit": [ + { + "CONCEPT_CODE": "/mg", + "CONCEPT_ID": 9256, + "CONCEPT_NAME": "per milligram", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "RangeLow": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "RangeHigh": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "MeasurementSourceConcept": 0, + "RangeLowRatio": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "RangeHighRatio": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Abnormal": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "1", + "CONCEPT_ID": 38004446, + "CONCEPT_NAME": "General Practice", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Medicare Specialty" + }, + { + "CONCEPT_CODE": "101YP1600X", + "CONCEPT_ID": 38003626, + "CONCEPT_NAME": "Pastoral Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + }, + { + "CONCEPT_CODE": "101YP2500X", + "CONCEPT_ID": 38003627, + "CONCEPT_NAME": "Professional Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "ERIP", + "CONCEPT_ID": 262, + "CONCEPT_NAME": "Emergency Room and Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + }, + { + "CONCEPT_CODE": "IP", + "CONCEPT_ID": 9201, + "CONCEPT_NAME": "Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Observation Test", + "description": "Observation Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Observation": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Observation": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "ObservationType": [ + { + "CONCEPT_CODE": "OMOP4822059", + "CONCEPT_ID": 38000183, + "CONCEPT_NAME": "Inpatient detail - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "ValueAsNumber": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "ValueAsString": { + "Text": "some value", + "Op": "contains" + }, + "ValueAsConcept": [ + { + "CONCEPT_CODE": "10000005", + "CONCEPT_ID": 36312355, + "CONCEPT_NAME": "17 ketosteroids urine", + "DOMAIN_ID": "Measurement", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "MedDRA" + } + ], + "Qualifier": [ + { + "CONCEPT_CODE": "276136004", + "CONCEPT_ID": 4172703, + "CONCEPT_NAME": "=", + "DOMAIN_ID": "Meas Value Operator", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + }, + { + "CONCEPT_CODE": "276137008", + "CONCEPT_ID": 4171754, + "CONCEPT_NAME": "<=", + "DOMAIN_ID": "Meas Value Operator", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Unit": [ + { + "CONCEPT_CODE": "%", + "CONCEPT_ID": 8554, + "CONCEPT_NAME": "percent", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "ObservationSourceConcept": 0, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "1", + "CONCEPT_ID": 38004446, + "CONCEPT_NAME": "General Practice", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Medicare Specialty" + }, + { + "CONCEPT_CODE": "101YP2500X", + "CONCEPT_ID": 38003627, + "CONCEPT_NAME": "Professional Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "ERIP", + "CONCEPT_ID": 262, + "CONCEPT_NAME": "Emergency Room and Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + }, + { + "CONCEPT_CODE": "IP", + "CONCEPT_ID": 9201, + "CONCEPT_NAME": "Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Observation Period Test", + "description": "Observation Period Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ObservationPeriod": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ObservationPeriod": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "First": true, + "PeriodStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "PeriodEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "PeriodType": [ + { + "CONCEPT_CODE": "OMOP4822059", + "CONCEPT_ID": 38000183, + "CONCEPT_NAME": "Inpatient detail - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "UserDefinedPeriod": { + "StartDate": "1900-01-01", + "EndDate": "2099-01-01" + }, + "AgeAtStart": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "AgeAtEnd": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "PeriodLength": { + "Value": 0, + "Extent": 99, + "Op": "bt" + } + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Procedure Occurrence Test", + "description": "Procedure Occurrence Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ProcedureOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ProcedureOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "ProcedureType": [ + { + "CONCEPT_CODE": "OMOP4822059", + "CONCEPT_ID": 38000183, + "CONCEPT_NAME": "Inpatient detail - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "Modifier": [ + { + "CONCEPT_CODE": "10013000", + "CONCEPT_ID": 4000804, + "CONCEPT_NAME": "Lateral meniscus structure", + "DOMAIN_ID": "Spec Anatomic Site", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + }, + { + "CONCEPT_CODE": "10024003", + "CONCEPT_ID": 4001771, + "CONCEPT_NAME": "Structure of base of lung", + "DOMAIN_ID": "Spec Anatomic Site", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "Quantity": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "1", + "CONCEPT_ID": 38004446, + "CONCEPT_NAME": "General Practice", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Medicare Specialty" + }, + { + "CONCEPT_CODE": "101YP2500X", + "CONCEPT_ID": 38003627, + "CONCEPT_NAME": "Professional Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "VisitType": [ + { + "CONCEPT_CODE": "ERIP", + "CONCEPT_ID": 262, + "CONCEPT_NAME": "Emergency Room and Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + }, + { + "CONCEPT_CODE": "IP", + "CONCEPT_ID": 9201, + "CONCEPT_NAME": "Inpatient Visit", + "DOMAIN_ID": "Visit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Visit" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Specimen Test", + "description": "Specimen Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Specimen": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "Specimen": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "SpecimenType": [ + { + "CONCEPT_CODE": "OMOP4822059", + "CONCEPT_ID": 38000183, + "CONCEPT_NAME": "Inpatient detail - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "Quantity": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Unit": [ + { + "CONCEPT_CODE": "%{baseline}", + "CONCEPT_ID": 8688, + "CONCEPT_NAME": "percent baseline", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + }, + { + "CONCEPT_CODE": "%{binding}", + "CONCEPT_ID": 9218, + "CONCEPT_NAME": "percent binding", + "DOMAIN_ID": "Unit", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "UCUM" + } + ], + "AnatomicSite": [ + { + "CONCEPT_CODE": "10036009", + "CONCEPT_ID": 4002601, + "CONCEPT_NAME": "Structure of nucleus pulposus of intervertebral disc of eighth thoracic vertebra", + "DOMAIN_ID": "Spec Anatomic Site", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + }, + { + "CONCEPT_CODE": "10042008", + "CONCEPT_ID": 4001422, + "CONCEPT_NAME": "Structure of intervertebral foramen of fifth thoracic vertebra", + "DOMAIN_ID": "Spec Anatomic Site", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + }, + { + "CONCEPT_CODE": "10047002", + "CONCEPT_ID": 4001433, + "CONCEPT_NAME": "Structure of transplanted lung", + "DOMAIN_ID": "Spec Anatomic Site", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "DiseaseStatus": [ + { + "CONCEPT_CODE": "21594007", + "CONCEPT_ID": 4066212, + "CONCEPT_NAME": "Malignant", + "DOMAIN_ID": "Spec Disease Status", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "SNOMED" + } + ], + "SourceId": { + "Text": "some sourceID", + "Op": "contains" + }, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ] + } + }, + "StartWindow": { + "Start": { + "Days": 30, + "Coeff": -1 + }, + "End": { + "Days": 30, + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Visit Test", + "description": "Visit Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "VisitOccurrence": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "VisitOccurrence": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "RestrictVisit": true, + "IgnoreObservationPeriod": true, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "OccurrenceStartDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Value": "1900-01-01", + "Extent": "2099-01-01", + "Op": "bt" + }, + "VisitType": [ + { + "CONCEPT_CODE": "OMOP4822059", + "CONCEPT_ID": 38000183, + "CONCEPT_NAME": "Inpatient detail - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + }, + { + "CONCEPT_CODE": "OMOP4822075", + "CONCEPT_ID": 38000199, + "CONCEPT_NAME": "Inpatient header - primary", + "DOMAIN_ID": "Type Concept", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Condition Type" + } + ], + "VisitSourceConcept": 0, + "VisitLength": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "First": true, + "Age": { + "Value": 0, + "Extent": 99, + "Op": "bt" + }, + "Gender": [ + { + "CONCEPT_CODE": "F", + "CONCEPT_ID": 8532, + "CONCEPT_NAME": "FEMALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + }, + { + "CONCEPT_CODE": "M", + "CONCEPT_ID": 8507, + "CONCEPT_NAME": "MALE", + "DOMAIN_ID": "Gender", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Gender" + } + ], + "ProviderSpecialty": [ + { + "CONCEPT_CODE": "1", + "CONCEPT_ID": 38004446, + "CONCEPT_NAME": "General Practice", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "Medicare Specialty" + }, + { + "CONCEPT_CODE": "101YP2500X", + "CONCEPT_ID": 38003627, + "CONCEPT_NAME": "Professional Counselor", + "DOMAIN_ID": "Provider", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "NUCC" + } + ], + "PlaceOfService": [ + { + "CONCEPT_CODE": "1021864", + "CONCEPT_ID": 42628591, + "CONCEPT_NAME": "Pharmacy", + "DOMAIN_ID": "Place of Service", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "CPT4" + }, + { + "CONCEPT_CODE": "1021865", + "CONCEPT_ID": 42628592, + "CONCEPT_NAME": "School", + "DOMAIN_ID": "Place of Service", + "INVALID_REASON_CAPTION": "Unknown", + "STANDARD_CONCEPT_CAPTION": "Unknown", + "VOCABULARY_ID": "CPT4" + } + ] + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 30, + "TimeUnit": "day", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 43200, + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + }, + { + "name": "Group Test", + "description": "Group Test", + "expression": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "ConditionOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 8640000, + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 144000, + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [ + { + "Type": "ANY", + "CriteriaList": [ + { + "Criteria": { + "ProcedureOccurrence": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 86400, + "TimeUnit": "minute", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 60, + "TimeUnit": "day", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, + { + "Criteria": { + "DrugExposure": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "TimeUnitValue": 2592000, + "TimeUnit": "second", + "Coeff": -1 + }, + "End": { + "TimeUnitValue": 43200, + "TimeUnit": "minute", + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + ] + } + } + ], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPadUnitValue": 0, + "EraPadUnit": "minute" + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.0.0" +} \ No newline at end of file diff --git a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json index 322a0f0c..915128f8 100644 --- a/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json +++ b/src/test/resources/cohortgeneration/eraDupes/eraDupes_PREP.json @@ -15,7 +15,6 @@ "condition_concept_id":2, "condition_start_date":"2009-06-16", "condition_end_date":"2009-06-19", - "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -24,7 +23,6 @@ "condition_concept_id":2, "condition_start_date":"2009-07-01", "condition_end_date":"2009-07-04", - "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -33,7 +31,6 @@ "condition_concept_id":3, "condition_start_date":"2009-07-27", "condition_end_date":"2009-07-30", - "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -42,7 +39,6 @@ "condition_concept_id":4, "condition_start_date":"2009-07-27", "condition_end_date":"2009-07-30", - "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 }, { @@ -51,7 +47,6 @@ "condition_concept_id":5, "condition_start_date":"2009-07-27", "condition_end_date":"2009-07-30", - "condition_start_datetime": "2009-06-19 00:00:00", "condition_type_concept_id":0 } ], From a674bfe56956427e4fb1c358e8fc45d4b033ff62 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Tue, 23 Jan 2024 12:02:43 +0100 Subject: [PATCH 19/25] Adding IntervalUnit to Criteria to specify datetime table columns while resolving the SELECT clause in SQL builders --- .../circe/cohortdefinition/Criteria.java | 10 ++++++++ .../ConditionOccurrenceSqlBuilder.java | 19 +++++++++----- .../builders/DeathSqlBuilder.java | 10 +++++++- .../builders/DeviceExposureSqlBuilder.java | 10 +++++++- .../builders/DrugExposureSqlBuilder.java | 10 +++++++- .../builders/MeasurementSqlBuilder.java | 10 +++++++- .../builders/ObservationSqlBuilder.java | 10 +++++++- .../ProcedureOccurrenceSqlBuilder.java | 10 +++++++- .../builders/SpecimenSqlBuilder.java | 25 +++++++++++++++++++ .../builders/VisitDetailSqlBuilder.java | 11 ++++++-- .../builders/VisitOccurrenceSqlBuilder.java | 11 ++++++-- 11 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java index dc64997b..9179bd83 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; + import org.ohdsi.circe.cohortdefinition.builders.BuilderOptions; /** @@ -60,5 +61,14 @@ public String accept(IGetCriteriaSqlDispatcher dispatcher) { @JsonProperty("DateAdjustment") public DateAdjustment dateAdjustment; + + /** + * This is a marker for the proper table columns definition while constructing the SELECT part of the result SQL + * in the corresponding SQL builder to reflect the values being defined in the associated WindowCriteria + * + * The DAY value should be set only if all the WindowCriteria's Window.Endpoint values are DAY + */ + @JsonProperty("IntervalUnit") + public String intervalUnit; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index d032505b..9d969aa0 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.helper.ResourceHelper; import java.text.SimpleDateFormat; @@ -93,13 +94,19 @@ protected List resolveSelectClauses(T criteria) { selectCols.add("co.condition_status_concept_id"); } // dateAdjustment or default start/end dates - if (criteria.dateAdjustment != null) { - selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, - criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", - criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); - } else { - selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + if (criteria.dateAdjustment != null) { + selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, + criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", + criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); + } else { + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + } + else { + // if any specific business logic is necessary if condition_end_datetime is empty it should be added accordingly as for the 'day' case + selectCols.add("co.condition_start_datetime as start_date, co.condition_end_datetime as end_date"); } + } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java index c79fc122..632c14fd 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.Death; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.helper.ResourceHelper; import java.util.ArrayList; @@ -9,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -78,7 +80,13 @@ protected List resolveSelectClauses(T criteria) { if (criteria.dateAdjustment != null) { selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, "d.death_date", "DATEADD(day,1,d.death_date)")); } else { - selectCols.add("d.death_date as start_date, DATEADD(day,1,d.death_date) as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("d.death_date as start_date, DATEADD(day,1,d.death_date) as end_date"); + } + else { + // if any specific business logic is necessary if death_datetime is empty it should be added accordingly + selectCols.add("d.death_datetime as start_date, d.death_datetime as end_date"); + } } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java index 586c15e7..a90ee542 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.DeviceExposure; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.helper.ResourceHelper; import java.util.ArrayList; @@ -10,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -102,7 +104,13 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "de.device_exposure_start_date" : "COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date))", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "de.device_exposure_start_date" : "COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date))")); } else { - selectCols.add("de.device_exposure_start_date as start_date, COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date)) as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("de.device_exposure_start_date as start_date, COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date)) as end_date"); + } + else { + // if any specific business logic is necessary if device_exposure_end_datetime is empty it should be added accordingly as for the 'day' case + selectCols.add("de.device_exposure_start_datetime as start_date, de.device_exposure_end_datetime as end_date"); + } } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java index 7a4a8045..25665d68 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.DrugExposure; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.helper.ResourceHelper; import java.util.ArrayList; @@ -9,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -130,7 +132,13 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "de.drug_exposure_start_date" : "COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date))")); } else { - selectCols.add("de.drug_exposure_start_date as start_date, COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date)) as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("de.drug_exposure_start_date as start_date, COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date)) as end_date"); + } + else { + // if any specific business logic is necessary if drug_exposure_end_datetime is empty it should be added accordingly as for the 'day' case + selectCols.add("de.drug_exposure_start_datetime as start_date, de.drug_exposure_end_datetime as end_date"); + } } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java index fe204467..8644e85b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java @@ -1,6 +1,7 @@ package org.ohdsi.circe.cohortdefinition.builders; import org.apache.commons.lang3.StringUtils; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.Measurement; import org.ohdsi.circe.helper.ResourceHelper; @@ -9,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -118,7 +120,13 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "m.measurement_date" : "DATEADD(day,1,m.measurement_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "m.measurement_date" : "DATEADD(day,1,m.measurement_date)")); } else { - selectCols.add("m.measurement_date as start_date, DATEADD(day,1,m.measurement_date) as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("m.measurement_date as start_date, DATEADD(day,1,m.measurement_date) as end_date"); + } + else { + // if any specific business logic is necessary if measurement_datetime is empty it should be added accordingly + selectCols.add("m.measurement_datetime as start_date, m.measurement_datetime as end_date"); + } } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java index 5206ef23..ce66dda3 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java @@ -1,6 +1,7 @@ package org.ohdsi.circe.cohortdefinition.builders; import org.apache.commons.lang3.StringUtils; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.Observation; import org.ohdsi.circe.helper.ResourceHelper; @@ -10,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -119,7 +121,13 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "o.observation_date" : "DATEADD(day,1,o.observation_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "o.observation_date" : "DATEADD(day,1,o.observation_date)")); } else { - selectCols.add("o.observation_date as start_date, DATEADD(day,1,o.observation_date) as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("o.observation_date as start_date, DATEADD(day,1,o.observation_date) as end_date"); + } + else { + // if any specific business logic is necessary if observation_datetime is empty it should be added accordingly + selectCols.add("o.observation_datetime as start_date, o.observation_datetime as end_date"); + } } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java index b6d30336..dc2d0c02 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java @@ -1,6 +1,7 @@ package org.ohdsi.circe.cohortdefinition.builders; import org.apache.commons.lang3.StringUtils; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; import org.ohdsi.circe.helper.ResourceHelper; @@ -9,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildDateRangeClause; @@ -102,7 +104,13 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "po.procedure_date" : "DATEADD(day,1,po.procedure_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "po.procedure_date" : "DATEADD(day,1,po.procedure_date)")); } else { - selectCols.add("po.procedure_date as start_date, DATEADD(day,1,po.procedure_date) as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("po.procedure_date as start_date, DATEADD(day,1,po.procedure_date) as end_date"); + } + else { + // if any specific business logic is necessary if procedure_end_datetime is empty it should be added accordingly + selectCols.add("po.procedure_datetime as start_date, po.procedure_end_datetime as end_date"); + } } return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java index ee8a802f..32524c92 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java @@ -1,6 +1,8 @@ package org.ohdsi.circe.cohortdefinition.builders; import org.apache.commons.lang3.StringUtils; +import org.ohdsi.circe.cohortdefinition.DateAdjustment; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.Specimen; import org.ohdsi.circe.helper.ResourceHelper; @@ -135,4 +137,27 @@ protected List resolveWhereClauses(T criteria) { return whereClauses; } + + @Override + protected List resolveSelectClauses(T criteria) { + // as this logic was fully missing comparing to the other SQL builders adding only the ones which belong to the datetime functionality + ArrayList selectCols = new ArrayList<>(); + + // dateAdjustment or default start/end dates + if (criteria.dateAdjustment != null) { + selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, + criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "s.specimen_date" : "DATEADD(day,1,s.specimen_date)", + criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "s.specimen_date" : "DATEADD(day,1,s.specimen_date)")); + } else { + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("s.specimen_date as start_date, DATEADD(day,1,s.specimen_date) as end_date"); + } + else { + // if any specific business logic is necessary if specimen_datetime is empty it should be added accordingly + selectCols.add("s.specimen_datetime as start_date, s.specimen_datetime as end_date"); + } + } + return selectCols; + } + } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java index 893b7cfd..a20216c7 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java @@ -2,10 +2,12 @@ import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.ConceptSetSelection; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.VisitDetail; import org.ohdsi.circe.helper.ResourceHelper; import java.util.*; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; public class VisitDetailSqlBuilder extends CriteriaSqlBuilder { @@ -90,9 +92,14 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "vd.visit_detail_start_date" : "vd.visit_detail_end_date", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "vd.visit_detail_start_date" : "vd.visit_detail_end_date")); } else { - selectCols.add("vd.visit_detail_start_date as start_date, vd.visit_detail_end_date as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("vd.visit_detail_start_date as start_date, vd.visit_detail_end_date as end_date"); + } + else { + // if any specific business logic is necessary if visit_detail_end_datetime is empty it should be added accordingly + selectCols.add("vd.visit_detail_start_datetime as start_date, vd.visit_detail_end_datetime as end_date"); + } } - return selectCols; } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java index 8780f808..651cdd8d 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java @@ -1,6 +1,7 @@ package org.ohdsi.circe.cohortdefinition.builders; import org.apache.commons.lang3.StringUtils; +import org.ohdsi.circe.cohortdefinition.IntervalUnit; import org.ohdsi.circe.cohortdefinition.VisitOccurrence; import org.ohdsi.circe.helper.ResourceHelper; @@ -9,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; + import org.ohdsi.circe.cohortdefinition.DateAdjustment; public class VisitOccurrenceSqlBuilder extends CriteriaSqlBuilder { @@ -92,9 +94,14 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "vo.visit_start_date" : "vo.visit_end_date", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "vo.visit_start_date" : "vo.visit_end_date")); } else { - selectCols.add("vo.visit_start_date as start_date, vo.visit_end_date as end_date"); + if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { + selectCols.add("vo.visit_start_date as start_date, vo.visit_end_date as end_date"); + } + else { + // if any specific business logic is necessary if visit_end_datetime is empty it should be added accordingly + selectCols.add("vo.visit_start_datetime as start_date, vo.visit_end_datetime as end_date"); + } } - return selectCols; } From 4ca364a9f1f66c32b7188cf9997699c9f0ca694e Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Mon, 5 Feb 2024 18:02:39 +0100 Subject: [PATCH 20/25] Handling the "UseDatetime" Cohort option so that the event datetime values from Criteria are available in the result cohort table --- .../circe/cohortdefinition/CohortExpression.java | 7 +++++++ .../CohortExpressionQueryBuilder.java | 14 ++++++++------ .../cohortdefinition/builders/BuilderOptions.java | 9 +++++++++ .../builders/ConditionEraSqlBuilder.java | 2 +- .../builders/ConditionOccurrenceSqlBuilder.java | 7 ++++--- .../builders/CriteriaSqlBuilder.java | 4 ++-- .../cohortdefinition/builders/DeathSqlBuilder.java | 7 ++++--- .../builders/DeviceExposureSqlBuilder.java | 7 ++++--- .../builders/DoseEraSqlBuilder.java | 2 +- .../builders/DrugEraSqlBuilder.java | 2 +- .../builders/DrugExposureSqlBuilder.java | 7 ++++--- .../builders/MeasurementSqlBuilder.java | 7 ++++--- .../builders/ObservationPeriodSqlBuilder.java | 2 +- .../builders/ObservationSqlBuilder.java | 7 ++++--- .../builders/PayerPlanPeriodSqlBuilder.java | 2 +- .../builders/ProcedureOccurrenceSqlBuilder.java | 7 ++++--- .../builders/SpecimenSqlBuilder.java | 7 ++++--- .../builders/VisitDetailSqlBuilder.java | 7 ++++--- .../builders/VisitOccurrenceSqlBuilder.java | 7 ++++--- 19 files changed, 71 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpression.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpression.java index d37b9777..b7b30c32 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpression.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpression.java @@ -72,6 +72,13 @@ public class CohortExpression implements CdmCompatibilitySpec { @JsonProperty("CensorWindow") public Period censorWindow; + + /** + * If specified the datetime columns should be used in Criteria instead of date + */ + @JsonProperty("UseDatetime") + public Boolean useDatetime; + @Override public String getCdmVersionRange() { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index b34dcf21..16a8ec00 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -177,23 +177,23 @@ public String getCodesetQuery(ConceptSet[] conceptSets) { } - private String getCensoringEventsQuery(Criteria[] censoringCriteria) { + private String getCensoringEventsQuery(Criteria[] censoringCriteria, BuilderOptions builderOptions) { ArrayList criteriaQueries = new ArrayList<>(); for (Criteria c : censoringCriteria) { - String criteriaQuery = c.accept(this); + String criteriaQuery = c.accept(this, builderOptions); criteriaQueries.add(StringUtils.replace(CENSORING_QUERY_TEMPLATE, "@criteriaQuery", criteriaQuery)); } return StringUtils.join(criteriaQueries, "\nUNION ALL\n"); } - public String getPrimaryEventsQuery(PrimaryCriteria primaryCriteria) { + public String getPrimaryEventsQuery(PrimaryCriteria primaryCriteria, BuilderOptions builderOptions) { String query = PRIMARY_EVENTS_TEMPLATE; ArrayList criteriaQueries = new ArrayList<>(); for (Criteria c : primaryCriteria.criteriaList) { - criteriaQueries.add(c.accept(this)); + criteriaQueries.add(c.accept(this, builderOptions)); } query = StringUtils.replace(query, "@criteriaQueries", StringUtils.join(criteriaQueries, "\nUNION ALL\n")); @@ -268,7 +268,9 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ String codesetQuery = getCodesetQuery(expression.conceptSets); resultSql = StringUtils.replace(resultSql, "@codesetQuery", codesetQuery); - String primaryEventsQuery = getPrimaryEventsQuery(expression.primaryCriteria); + BuilderOptions builderOptions = new BuilderOptions(); + builderOptions.setUseDatetime(expression.useDatetime); + String primaryEventsQuery = getPrimaryEventsQuery(expression.primaryCriteria, builderOptions); resultSql = StringUtils.replace(resultSql, "@primaryEventsQuery", primaryEventsQuery); String additionalCriteriaQuery = ""; @@ -344,7 +346,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ } if (expression.censoringCriteria != null && expression.censoringCriteria.length > 0) { - endDateSelects.add(String.format("-- Censor Events\n%s\n", getCensoringEventsQuery(expression.censoringCriteria))); + endDateSelects.add(String.format("-- Censor Events\n%s\n", getCensoringEventsQuery(expression.censoringCriteria, builderOptions))); } resultSql = StringUtils.replace(resultSql, "@finalCohortQuery", getFinalCohortQuery(expression.censorWindow)); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderOptions.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderOptions.java index 2b0f2a45..1b777e5a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderOptions.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderOptions.java @@ -6,5 +6,14 @@ public class BuilderOptions { public List additionalColumns = new ArrayList<>(); + + private boolean useDatetime; + public boolean isUseDatetime() { + return useDatetime; + } + + public void setUseDatetime(Boolean useDatetime) { + this.useDatetime = useDatetime == null ? false: useDatetime; + } } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionEraSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionEraSqlBuilder.java index 4f6183f3..364e0ca0 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionEraSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionEraSqlBuilder.java @@ -73,7 +73,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); // dateAdjustment or default start/end dates diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java index 9d969aa0..314a3541 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ConditionOccurrenceSqlBuilder.java @@ -75,7 +75,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); // Condition Type if (criteria.conditionType != null && criteria.conditionType.length > 0) { @@ -99,8 +99,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "co.condition_start_date" : "COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("co.condition_start_date as start_date, COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date)) as end_date"); } else { // if any specific business logic is necessary if condition_end_datetime is empty it should be added accordingly as for the 'day' case diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaSqlBuilder.java index 32dd1051..332e4271 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaSqlBuilder.java @@ -20,7 +20,7 @@ public String getCriteriaSql(T criteria, BuilderOptions options) { query = embedCodesetClause(query, criteria); - List selectClauses = resolveSelectClauses(criteria); + List selectClauses = resolveSelectClauses(criteria, options); List joinClauses = resolveJoinClauses(criteria); List whereClauses = resolveWhereClauses(criteria); @@ -82,7 +82,7 @@ protected String embedWhereClauses(String query, List whereClauses) { protected abstract String embedOrdinalExpression(String query, T criteria, List whereClauses); - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { return new ArrayList(); } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java index 632c14fd..77476891 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeathSqlBuilder.java @@ -69,7 +69,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); // Condition Type if (criteria.deathType != null && criteria.deathType.length > 0) { @@ -80,8 +80,9 @@ protected List resolveSelectClauses(T criteria) { if (criteria.dateAdjustment != null) { selectCols.add(BuilderUtils.getDateAdjustmentExpression(criteria.dateAdjustment, "d.death_date", "DATEADD(day,1,d.death_date)")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("d.death_date as start_date, DATEADD(day,1,d.death_date) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("d.death_date as start_date, DATEADD(day,1,d.death_date) as end_date"); } else { // if any specific business logic is necessary if death_datetime is empty it should be added accordingly diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java index a90ee542..c4b8bbd8 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DeviceExposureSqlBuilder.java @@ -81,7 +81,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); // Device Type if (criteria.deviceType != null && criteria.deviceType.length > 0) { @@ -104,8 +104,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "de.device_exposure_start_date" : "COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date))", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "de.device_exposure_start_date" : "COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date))")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("de.device_exposure_start_date as start_date, COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date)) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("de.device_exposure_start_date as start_date, COALESCE(de.device_exposure_end_date, DATEADD(day,1,de.device_exposure_start_date)) as end_date"); } else { // if any specific business logic is necessary if device_exposure_end_datetime is empty it should be added accordingly as for the 'day' case diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DoseEraSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DoseEraSqlBuilder.java index 3f473f86..4c459085 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DoseEraSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DoseEraSqlBuilder.java @@ -90,7 +90,7 @@ protected List resolveJoinClauses(T criteria) { } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugEraSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugEraSqlBuilder.java index acb00829..ab562d66 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugEraSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugEraSqlBuilder.java @@ -77,7 +77,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java index 25665d68..c8718253 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/DrugExposureSqlBuilder.java @@ -85,7 +85,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); @@ -132,8 +132,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "de.drug_exposure_start_date" : "COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date))")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("de.drug_exposure_start_date as start_date, COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date)) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("de.drug_exposure_start_date as start_date, COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date)) as end_date"); } else { // if any specific business logic is necessary if drug_exposure_end_datetime is empty it should be added accordingly as for the 'day' case diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java index 8644e85b..374c525f 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java @@ -85,7 +85,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); @@ -120,8 +120,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "m.measurement_date" : "DATEADD(day,1,m.measurement_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "m.measurement_date" : "DATEADD(day,1,m.measurement_date)")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("m.measurement_date as start_date, DATEADD(day,1,m.measurement_date) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("m.measurement_date as start_date, DATEADD(day,1,m.measurement_date) as end_date"); } else { // if any specific business logic is necessary if measurement_datetime is empty it should be added accordingly diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationPeriodSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationPeriodSqlBuilder.java index d403be12..931317fc 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationPeriodSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationPeriodSqlBuilder.java @@ -79,7 +79,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java index ce66dda3..7db49ea6 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ObservationSqlBuilder.java @@ -82,7 +82,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); @@ -121,8 +121,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "o.observation_date" : "DATEADD(day,1,o.observation_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "o.observation_date" : "DATEADD(day,1,o.observation_date)")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("o.observation_date as start_date, DATEADD(day,1,o.observation_date) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("o.observation_date as start_date, DATEADD(day,1,o.observation_date) as end_date"); } else { // if any specific business logic is necessary if observation_datetime is empty it should be added accordingly diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/PayerPlanPeriodSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/PayerPlanPeriodSqlBuilder.java index 6b0b5135..9f67a0bf 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/PayerPlanPeriodSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/PayerPlanPeriodSqlBuilder.java @@ -76,7 +76,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java index dc2d0c02..12ec398a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java @@ -79,7 +79,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); @@ -104,8 +104,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "po.procedure_date" : "DATEADD(day,1,po.procedure_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "po.procedure_date" : "DATEADD(day,1,po.procedure_date)")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("po.procedure_date as start_date, DATEADD(day,1,po.procedure_date) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("po.procedure_date as start_date, DATEADD(day,1,po.procedure_date) as end_date"); } else { // if any specific business logic is necessary if procedure_end_datetime is empty it should be added accordingly diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java index 32524c92..a1616d4b 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/SpecimenSqlBuilder.java @@ -139,7 +139,7 @@ protected List resolveWhereClauses(T criteria) { } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { // as this logic was fully missing comparing to the other SQL builders adding only the ones which belong to the datetime functionality ArrayList selectCols = new ArrayList<>(); @@ -149,8 +149,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "s.specimen_date" : "DATEADD(day,1,s.specimen_date)", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "s.specimen_date" : "DATEADD(day,1,s.specimen_date)")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("s.specimen_date as start_date, DATEADD(day,1,s.specimen_date) as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("s.specimen_date as start_date, DATEADD(day,1,s.specimen_date) as end_date"); } else { // if any specific business logic is necessary if specimen_datetime is empty it should be added accordingly diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java index a20216c7..e366d543 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java @@ -67,7 +67,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); @@ -92,8 +92,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "vd.visit_detail_start_date" : "vd.visit_detail_end_date", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "vd.visit_detail_start_date" : "vd.visit_detail_end_date")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("vd.visit_detail_start_date as start_date, vd.visit_detail_end_date as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("vd.visit_detail_start_date as start_date, vd.visit_detail_end_date as end_date"); } else { // if any specific business logic is necessary if visit_detail_end_datetime is empty it should be added accordingly diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java index 651cdd8d..ad0047db 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitOccurrenceSqlBuilder.java @@ -69,7 +69,7 @@ protected String embedOrdinalExpression(String query, T criteria, List w } @Override - protected List resolveSelectClauses(T criteria) { + protected List resolveSelectClauses(T criteria, BuilderOptions builderOptions) { ArrayList selectCols = new ArrayList<>(DEFAULT_SELECT_COLUMNS); @@ -94,8 +94,9 @@ protected List resolveSelectClauses(T criteria) { criteria.dateAdjustment.startWith == DateAdjustment.DateType.START_DATE ? "vo.visit_start_date" : "vo.visit_end_date", criteria.dateAdjustment.endWith == DateAdjustment.DateType.START_DATE ? "vo.visit_start_date" : "vo.visit_end_date")); } else { - if (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit)) { - selectCols.add("vo.visit_start_date as start_date, vo.visit_end_date as end_date"); + if ((builderOptions == null || !builderOptions.isUseDatetime()) && + (criteria.intervalUnit == null || IntervalUnit.DAY.getName().equals(criteria.intervalUnit))) { + selectCols.add("vo.visit_start_date as start_date, vo.visit_end_date as end_date"); } else { // if any specific business logic is necessary if visit_end_datetime is empty it should be added accordingly From 3a4786d5c397db703ada8af0cf3d1de4fda2e305 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Mon, 19 Feb 2024 18:31:31 +0100 Subject: [PATCH 21/25] Leaving intermediate tables undeleted --- .../CohortExpressionQueryBuilder.java | 4 +-- .../sql/customEraStrategy.sql | 4 +-- .../cohortdefinition/sql/generateCohort.sql | 28 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 16a8ec00..c98e9ec5 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -310,7 +310,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ inclusionRuleInserts.add(String.format("SELECT inclusion_rule_id, person_id, event_id\nINTO #inclusion_events\nFROM (%s) I;", irTempUnion)); inclusionRuleInserts.addAll(inclusionRuleTempTables.stream() - .map(d -> String.format("TRUNCATE TABLE %s;\nDROP TABLE %s;\n", d, d)) + .map(d -> String.format("-- TRUNCATE TABLE %s;\nDROP TABLE %s;\n", d, d)) .collect(Collectors.toList()) ); resultSql = StringUtils.replace(resultSql, "@inclusionCohortInserts", StringUtils.join(inclusionRuleInserts, "\n")); @@ -337,7 +337,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ if (expression.endStrategy != null) { // replace @strategy_ends placeholders with temp table creation and cleanup scripts. resultSql = StringUtils.replace(resultSql, "@strategy_ends_temp_tables", expression.endStrategy.accept(this, "#included_events")); - resultSql = StringUtils.replace(resultSql, "@strategy_ends_cleanup", "TRUNCATE TABLE #strategy_ends;\nDROP TABLE #strategy_ends;\n"); + resultSql = StringUtils.replace(resultSql, "@strategy_ends_cleanup", ""); // "TRUNCATE TABLE #strategy_ends;\nDROP TABLE #strategy_ends;\n"); endDateSelects.add(String.format("-- End Date Strategy\n%s\n", "SELECT event_id, person_id, end_date from #strategy_ends")); } else { // replace @trategy_ends placeholders with empty string diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index 3bde650c..7eee7e5e 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -61,5 +61,5 @@ JOIN ) ERAS on ERAS.person_id = et.person_id WHERE et.start_date between ERAS.era_start_date and ERAS.era_end_date; -TRUNCATE TABLE #drugTarget; -DROP TABLE #drugTarget; +-- TRUNCATE TABLE #drugTarget; +-- DROP TABLE #drugTarget; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index c49a855f..46f6281d 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -143,29 +143,29 @@ WHERE ranked.rank_value = 1 @inclusionImpactAnalysisByPersonQuery -- END: Inclusion Impact Analysis - person -TRUNCATE TABLE #best_events; -DROP TABLE #best_events; +-- TRUNCATE TABLE #best_events; +-- DROP TABLE #best_events; -TRUNCATE TABLE #inclusion_rules; -DROP TABLE #inclusion_rules; +-- TRUNCATE TABLE #inclusion_rules; +-- DROP TABLE #inclusion_rules; } @strategy_ends_cleanup -TRUNCATE TABLE #cohort_rows; -DROP TABLE #cohort_rows; +-- TRUNCATE TABLE #cohort_rows; +-- DROP TABLE #cohort_rows; -TRUNCATE TABLE #final_cohort; -DROP TABLE #final_cohort; +-- TRUNCATE TABLE #final_cohort; +-- DROP TABLE #final_cohort; -TRUNCATE TABLE #inclusion_events; -DROP TABLE #inclusion_events; +-- TRUNCATE TABLE #inclusion_events; +-- DROP TABLE #inclusion_events; -TRUNCATE TABLE #qualified_events; -DROP TABLE #qualified_events; +-- TRUNCATE TABLE #qualified_events; +-- DROP TABLE #qualified_events; -TRUNCATE TABLE #included_events; -DROP TABLE #included_events; +-- TRUNCATE TABLE #included_events; +-- DROP TABLE #included_events; TRUNCATE TABLE #Codesets; DROP TABLE #Codesets; From 6acb70d3be8de415a36046aebac84ef9115b1435 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Fri, 23 Feb 2024 15:43:15 +0100 Subject: [PATCH 22/25] Supplementing places which didn't handle useDatetime from Cohort Expression properly --- .../CohortExpressionQueryBuilder.java | 32 +++++++++++-------- .../CorelatedCriteria_5_0_0_Test.java | 10 +++--- .../CorelatedCriteria_5_3_0_Test.java | 12 +++---- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index c98e9ec5..4737251f 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -20,11 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; + import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.builders.*; import org.ohdsi.circe.helper.ResourceHelper; @@ -144,9 +146,9 @@ private String getAdditionalColumns(List columns, String prefix) return String.join(",", columns.stream().map((column) -> { return prefix + column.columnName();}).collect(Collectors.toList())); } - private String wrapCriteriaQuery(String query, CriteriaGroup group) { + private String wrapCriteriaQuery(String query, CriteriaGroup group, Boolean useDatetime) { String eventQuery = StringUtils.replace(EVENT_TABLE_EXPRESSION_TEMPLATE, "@eventQuery", query); - String groupQuery = this.getCriteriaGroupQuery(group, String.format("(%s)", eventQuery)); + String groupQuery = this.getCriteriaGroupQuery(group, String.format("(%s)", eventQuery), useDatetime); groupQuery = StringUtils.replace(groupQuery, "@indexId", "" + 0); String wrappedQuery = String.format( "select PE.person_id, PE.event_id, PE.start_date, PE.end_date, PE.visit_occurrence_id, PE.sort_date FROM (\n%s\n) PE\nJOIN (\n%s) AC on AC.person_id = pe.person_id and AC.event_id = pe.event_id\n", @@ -276,7 +278,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ String additionalCriteriaQuery = ""; if (expression.additionalCriteria != null && !expression.additionalCriteria.isEmpty()) { CriteriaGroup acGroup = expression.additionalCriteria; - String acGroupQuery = this.getCriteriaGroupQuery(acGroup, String.format("(%s)", primaryEventsQuery));//acGroup.accept(this); + String acGroupQuery = this.getCriteriaGroupQuery(acGroup, String.format("(%s)", primaryEventsQuery), expression.useDatetime);//acGroup.accept(this); acGroupQuery = StringUtils.replace(acGroupQuery, "@indexId", "" + 0); additionalCriteriaQuery = "\nJOIN (\n" + acGroupQuery + ") AC on AC.person_id = pe.person_id and AC.event_id = pe.event_id\n"; } @@ -297,7 +299,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ for (int i = 0; i < expression.inclusionRules.size(); i++) { CriteriaGroup cg = expression.inclusionRules.get(i).expression; - String inclusionRuleInsert = getInclusionRuleQuery(cg); + String inclusionRuleInsert = getInclusionRuleQuery(cg, expression.useDatetime); inclusionRuleInsert = StringUtils.replace(inclusionRuleInsert, "@inclusion_rule_id", "" + i); inclusionRuleInserts.add(inclusionRuleInsert); inclusionRuleTempTables.add(String.format("#Inclusion_%d", i)); @@ -402,20 +404,22 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ return resultSql; } - public String getCriteriaGroupQuery(CriteriaGroup group, String eventTable) { + public String getCriteriaGroupQuery(CriteriaGroup group, String eventTable, Boolean useDatetime) { String query = GROUP_QUERY_TEMPLATE; ArrayList additionalCriteriaQueries = new ArrayList<>(); String joinType = "INNER"; int indexId = 0; for (CorelatedCriteria cc : group.criteriaList) { - String acQuery = this.getCorelatedlCriteriaQuery(cc, eventTable); //ac.accept(this); + String acQuery = this.getCorelatedlCriteriaQuery(cc, eventTable, useDatetime); //ac.accept(this); acQuery = StringUtils.replace(acQuery, "@indexId", "" + indexId); additionalCriteriaQueries.add(acQuery); indexId++; } for (DemographicCriteria dc : group.demographicCriteriaList) { + // the Demographics Criteria refers to an event date/datetime alias start_date, end_date + // therefore the useDatetime logic is irrelevant at this place!? String dcQuery = this.getDemographicCriteriaQuery(dc, eventTable); //ac.accept(this); dcQuery = StringUtils.replace(dcQuery, "@indexId", "" + indexId); additionalCriteriaQueries.add(dcQuery); @@ -423,7 +427,7 @@ public String getCriteriaGroupQuery(CriteriaGroup group, String eventTable) { } for (CriteriaGroup g : group.groups) { - String gQuery = this.getCriteriaGroupQuery(g, eventTable); //g.accept(this); + String gQuery = this.getCriteriaGroupQuery(g, eventTable, useDatetime); //g.accept(this); gQuery = StringUtils.replace(gQuery, "@indexId", "" + indexId); additionalCriteriaQueries.add(gQuery); indexId++; @@ -469,9 +473,9 @@ public String getCriteriaGroupQuery(CriteriaGroup group, String eventTable) { return query; } - private String getInclusionRuleQuery(CriteriaGroup inclusionRule) { + private String getInclusionRuleQuery(CriteriaGroup inclusionRule, Boolean useDatetime) { String resultSql = INCLUSION_RULE_QUERY_TEMPLATE; - String additionalCriteriaQuery = "\nJOIN (\n" + getCriteriaGroupQuery(inclusionRule, "#qualified_events") + ") AC on AC.person_id = pe.person_id AND AC.event_id = pe.event_id"; + String additionalCriteriaQuery = "\nJOIN (\n" + getCriteriaGroupQuery(inclusionRule, "#qualified_events", useDatetime) + ") AC on AC.person_id = pe.person_id AND AC.event_id = pe.event_id"; additionalCriteriaQuery = StringUtils.replace(additionalCriteriaQuery, "@indexId", "" + 0); resultSql = StringUtils.replace(resultSql, "@additionalCriteriaQuery", additionalCriteriaQuery); return resultSql; @@ -634,7 +638,7 @@ public String getWindowedCriteriaQuery(WindowedCriteria criteria, String eventTa return query; } - public String getCorelatedlCriteriaQuery(CorelatedCriteria corelatedCriteria, String eventTable) { + public String getCorelatedlCriteriaQuery(CorelatedCriteria corelatedCriteria, String eventTable, Boolean useDatetime) { // pick the appropraite query template that is optimized for include (at least 1) or exclude (allow 0) String query = (corelatedCriteria.occurrence.type == Occurrence.AT_MOST || corelatedCriteria.occurrence.count == 0) ? ADDITIONAL_CRITERIA_LEFT_TEMPLATE : ADDITIONAL_CRITERIA_INNER_TEMPLATE; @@ -642,6 +646,8 @@ public String getCorelatedlCriteriaQuery(CorelatedCriteria corelatedCriteria, St String countColumnExpression = "cc.event_id"; BuilderOptions builderOptions = new BuilderOptions(); + // a part of the Cohort Expression design being passed + builderOptions.setUseDatetime(useDatetime); if (corelatedCriteria.occurrence.isDistinct) { if (corelatedCriteria.occurrence.countColumn == null) { // backwards compatability: default column uses domain_concept_id builderOptions.additionalColumns.add(CriteriaColumn.DOMAIN_CONCEPT); @@ -673,16 +679,16 @@ public String getCorelatedlCriteriaQuery(CorelatedCriteria corelatedCriteria, St protected String getCriteriaSql(CriteriaSqlBuilder builder, T criteria, BuilderOptions options) { String query = builder.getCriteriaSql(criteria, options); - return processCorrelatedCriteria(query, criteria); + return processCorrelatedCriteria(query, criteria, options == null ? false : options.isUseDatetime()); } protected String getCriteriaSql(CriteriaSqlBuilder builder, T criteria) { return this.getCriteriaSql(builder, criteria, null); } - protected String processCorrelatedCriteria(String query, Criteria criteria) { + protected String processCorrelatedCriteria(String query, Criteria criteria, Boolean useDatetime) { if (criteria.CorrelatedCriteria != null && !criteria.CorrelatedCriteria.isEmpty()) { - query = wrapCriteriaQuery(query, criteria.CorrelatedCriteria); + query = wrapCriteriaQuery(query, criteria.CorrelatedCriteria, useDatetime); } return query; } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_0_0_Test.java index dfcfbc79..952c01e4 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_0_0_Test.java @@ -71,7 +71,7 @@ public void simpleInclusionTest() throws Exception { // translate to PG String eventTable = String.format(CriteriaUtils.EVENT_TABLE_TEMPLATE, RESULTS_SCHEMA + ".cohort", "cdm", 1); - String inclusionQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String inclusionQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); String translatedInclusionQuery = SqlRender.renderSql(SqlTranslate.translateSql(inclusionQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, new String[] {"cdm", "0"}); @@ -87,7 +87,7 @@ public void simpleInclusionTest() throws Exception { // perform exclusion query by changing the occurrence type in the CorelatedCriteria // Note this is still an 'inclusion', just only include those with exactly 0 counts cc.occurrence = CriteriaUtils.getAtExactly0Occurrence(); - String exclusionQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String exclusionQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); String translatedExclusionQuery = SqlRender.renderSql(SqlTranslate.translateSql(exclusionQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, new String[] {"cdm", "0"}); @@ -132,7 +132,7 @@ public void distinctStartTest() throws Exception { // translate to PG String eventTable = String.format(CriteriaUtils.EVENT_TABLE_TEMPLATE, RESULTS_SCHEMA + ".cohort", "cdm", 1); - String countQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String countQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); String translatedCountQuery = SqlRender.renderSql(SqlTranslate.translateSql(countQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, new String[] {"cdm", "0"}); @@ -178,7 +178,7 @@ public void distinctVisitTest() throws Exception { // translate to PG String eventTable = String.format(CriteriaUtils.EVENT_TABLE_TEMPLATE, RESULTS_SCHEMA + ".cohort", "cdm", 1); - String countQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String countQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); String translatedCountQuery = SqlRender.renderSql(SqlTranslate.translateSql(countQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, new String[] {"cdm", "0"}); @@ -224,7 +224,7 @@ public void distinctDefaultTest() throws Exception { // translate to PG String eventTable = String.format(CriteriaUtils.EVENT_TABLE_TEMPLATE, RESULTS_SCHEMA + ".cohort", "cdm", 1); - String countQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String countQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); String translatedCountQuery = SqlRender.renderSql(SqlTranslate.translateSql(countQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, new String[] {"cdm", "0"}); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_3_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_3_0_Test.java index c762aaa6..ce8b289b 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_3_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_3_0_Test.java @@ -88,7 +88,7 @@ public void visitDetailCodesetCriteriaTest() throws Exception { visitDetail.genderCS = inCsSelection; // translate to PG - String inGenderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String inGenderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); inGenderQuery = inGenderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); String translatedInGenderQuery = SqlRender.renderSql(SqlTranslate.translateSql(inGenderQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, @@ -104,7 +104,7 @@ public void visitDetailCodesetCriteriaTest() throws Exception { visitDetail.genderCS = notInCsSelection; // translate to PG - String notInGenderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String notInGenderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); notInGenderQuery = notInGenderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); String translatedNotInGenderQuery = SqlRender.renderSql(SqlTranslate.translateSql(notInGenderQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, @@ -121,7 +121,7 @@ public void visitDetailCodesetCriteriaTest() throws Exception { visitDetail.providerSpecialtyCS = inCsSelection; // translate to PG - String inProviderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String inProviderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); inProviderQuery = inProviderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); String translatedInProviderQuery = SqlRender.renderSql(SqlTranslate.translateSql(inProviderQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, @@ -137,7 +137,7 @@ public void visitDetailCodesetCriteriaTest() throws Exception { visitDetail.providerSpecialtyCS = notInCsSelection; // translate to PG - String notInProviderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String notInProviderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); notInProviderQuery = notInProviderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); String translatedNotInProviderQuery = SqlRender.renderSql(SqlTranslate.translateSql(notInProviderQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, @@ -154,7 +154,7 @@ public void visitDetailCodesetCriteriaTest() throws Exception { visitDetail.placeOfServiceCS = inCsSelection; // translate to PG - String inPlaceOfServiceQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String inPlaceOfServiceQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); inPlaceOfServiceQuery = inPlaceOfServiceQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); String translatedInPlaceOfServiceQuery = SqlRender.renderSql(SqlTranslate.translateSql(inPlaceOfServiceQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, @@ -170,7 +170,7 @@ public void visitDetailCodesetCriteriaTest() throws Exception { visitDetail.placeOfServiceCS = notInCsSelection; // translate to PG - String notInPlaceOfServiceQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + String notInPlaceOfServiceQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable, null); notInPlaceOfServiceQuery = notInPlaceOfServiceQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); String translatedNotInPlaceOfServiceQuery = SqlRender.renderSql(SqlTranslate.translateSql(notInPlaceOfServiceQuery, "postgresql"), new String[] {"cdm_database_schema", "indexId"}, From caca4159575e4cb0257ba7c0c0f0917a3191cec3 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Mon, 20 May 2024 14:45:55 +0200 Subject: [PATCH 23/25] OMOP CDM 5.3.1 doesn't have procedure_end_datetime so that using procedure_datetime instead --- .../builders/ProcedureOccurrenceSqlBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java index 12ec398a..1032cd43 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/ProcedureOccurrenceSqlBuilder.java @@ -110,7 +110,7 @@ protected List resolveSelectClauses(T criteria, BuilderOptions builderOp } else { // if any specific business logic is necessary if procedure_end_datetime is empty it should be added accordingly - selectCols.add("po.procedure_datetime as start_date, po.procedure_end_datetime as end_date"); + selectCols.add("po.procedure_datetime as start_date, po.procedure_datetime as end_date"); } } return selectCols; From 6978dbc69ebc4dfc5540d1ffb0f1ca10210483de Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Mon, 15 Jul 2024 11:32:03 +0200 Subject: [PATCH 24/25] Reverting back temporary tables truncating and deleting which was commented for experimental purposes --- .../CohortExpressionQueryBuilder.java | 4 +-- .../sql/customEraStrategy.sql | 4 +-- .../cohortdefinition/sql/generateCohort.sql | 28 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 4737251f..2c77cc85 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -312,7 +312,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ inclusionRuleInserts.add(String.format("SELECT inclusion_rule_id, person_id, event_id\nINTO #inclusion_events\nFROM (%s) I;", irTempUnion)); inclusionRuleInserts.addAll(inclusionRuleTempTables.stream() - .map(d -> String.format("-- TRUNCATE TABLE %s;\nDROP TABLE %s;\n", d, d)) + .map(d -> String.format("TRUNCATE TABLE %s;\nDROP TABLE %s;\n", d, d)) .collect(Collectors.toList()) ); resultSql = StringUtils.replace(resultSql, "@inclusionCohortInserts", StringUtils.join(inclusionRuleInserts, "\n")); @@ -339,7 +339,7 @@ public String buildExpressionQuery(CohortExpression expression, BuildExpressionQ if (expression.endStrategy != null) { // replace @strategy_ends placeholders with temp table creation and cleanup scripts. resultSql = StringUtils.replace(resultSql, "@strategy_ends_temp_tables", expression.endStrategy.accept(this, "#included_events")); - resultSql = StringUtils.replace(resultSql, "@strategy_ends_cleanup", ""); // "TRUNCATE TABLE #strategy_ends;\nDROP TABLE #strategy_ends;\n"); + resultSql = StringUtils.replace(resultSql, "@strategy_ends_cleanup", "TRUNCATE TABLE #strategy_ends;\nDROP TABLE #strategy_ends;\n"); endDateSelects.add(String.format("-- End Date Strategy\n%s\n", "SELECT event_id, person_id, end_date from #strategy_ends")); } else { // replace @trategy_ends placeholders with empty string diff --git a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql index 7eee7e5e..3bde650c 100644 --- a/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql +++ b/src/main/resources/resources/cohortdefinition/sql/customEraStrategy.sql @@ -61,5 +61,5 @@ JOIN ) ERAS on ERAS.person_id = et.person_id WHERE et.start_date between ERAS.era_start_date and ERAS.era_end_date; --- TRUNCATE TABLE #drugTarget; --- DROP TABLE #drugTarget; +TRUNCATE TABLE #drugTarget; +DROP TABLE #drugTarget; diff --git a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql index 46f6281d..c49a855f 100644 --- a/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql +++ b/src/main/resources/resources/cohortdefinition/sql/generateCohort.sql @@ -143,29 +143,29 @@ WHERE ranked.rank_value = 1 @inclusionImpactAnalysisByPersonQuery -- END: Inclusion Impact Analysis - person --- TRUNCATE TABLE #best_events; --- DROP TABLE #best_events; +TRUNCATE TABLE #best_events; +DROP TABLE #best_events; --- TRUNCATE TABLE #inclusion_rules; --- DROP TABLE #inclusion_rules; +TRUNCATE TABLE #inclusion_rules; +DROP TABLE #inclusion_rules; } @strategy_ends_cleanup --- TRUNCATE TABLE #cohort_rows; --- DROP TABLE #cohort_rows; +TRUNCATE TABLE #cohort_rows; +DROP TABLE #cohort_rows; --- TRUNCATE TABLE #final_cohort; --- DROP TABLE #final_cohort; +TRUNCATE TABLE #final_cohort; +DROP TABLE #final_cohort; --- TRUNCATE TABLE #inclusion_events; --- DROP TABLE #inclusion_events; +TRUNCATE TABLE #inclusion_events; +DROP TABLE #inclusion_events; --- TRUNCATE TABLE #qualified_events; --- DROP TABLE #qualified_events; +TRUNCATE TABLE #qualified_events; +DROP TABLE #qualified_events; --- TRUNCATE TABLE #included_events; --- DROP TABLE #included_events; +TRUNCATE TABLE #included_events; +DROP TABLE #included_events; TRUNCATE TABLE #Codesets; DROP TABLE #Codesets; From 674b028a2911b052a0b74750d52e6b272524d16b Mon Sep 17 00:00:00 2001 From: fedor-g Date: Mon, 22 Jul 2024 17:15:15 +0300 Subject: [PATCH 25/25] issues-2886: Comparisons fix --- .../circe/check/checkers/Comparisons.java | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java index 8f54990c..0af3a208 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -20,22 +20,17 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; - import org.apache.commons.lang3.builder.EqualsBuilder; import org.ohdsi.circe.cohortdefinition.*; import org.ohdsi.circe.vocabulary.Concept; public class Comparisons { - private static final Map TIME_UNIT_CONVERSION = new HashMap<>(); - static { - TIME_UNIT_CONVERSION.put(IntervalUnit.HOUR.getName(), 60 * 60); - TIME_UNIT_CONVERSION.put(IntervalUnit.MINUTE.getName(), 60); - } - public static Boolean startIsGreaterThanEnd(NumericRange r) { return Objects.nonNull(r.value) && Objects.nonNull(r.extent) && r.value.intValue() > r.extent.intValue(); @@ -107,26 +102,48 @@ public static Predicate compare(Concept source) { .build(); } + /** + * If timeUnit is equal to values such as Hours, Minutes, Seconds, the values will be converted to seconds + * Otherwise it will return to the previous logic. + * @param filter + * @param window + * @return + */ public static int compareTo(ObservationFilter filter, Window window) { - int range1, range2Start, range2End; - if (Objects.nonNull(window.start) && (Objects.nonNull(window.start.days) || IntervalUnit.DAY.getName().equals(window.start.timeUnit))) { - range1 = filter.postDays + filter.priorDays; - range2Start = window.start.coeff * window.start.days; - range2End = Objects.nonNull(window.end) && Objects.nonNull(window.end.days) ? window.end.coeff * window.end.days : 0; - } else { + int range1, range2Start = 0, range2End = 0; + if (Objects.nonNull(window.start) && Objects.nonNull(window.start.timeUnit) && !IntervalUnit.DAY.getName().equals(window.start.timeUnit)) { range1 = (filter.postDays + filter.priorDays) * 24 * 60 * 60; range2Start = getTimeInSeconds(window.start); range2End = getTimeInSeconds(window.end); + } else { + range1 = filter.postDays + filter.priorDays; + if (Objects.nonNull(window.start) && Objects.nonNull(window.start.days)) { + range2Start = window.start.coeff * window.start.days; + } + if (Objects.nonNull(window.end) && Objects.nonNull(window.end.days)) { + range2End = window.end.coeff * window.end.days; + } } return range1 - (range2End - range2Start); } - private static int getTimeInSeconds(Window.Endpoint endpoint) { - return Optional.ofNullable(endpoint) - .map(ep -> { - int convertRate = TIME_UNIT_CONVERSION.getOrDefault(ep.timeUnit, 1); - return Objects.nonNull(ep.timeUnitValue) ? ep.coeff * ep.timeUnitValue * convertRate : 0; - }).orElse(0); - } + + /** + * @return Convert values to seconds. + */ + private static int getTimeInSeconds(Window.Endpoint endpoint) { + if (Objects.isNull(endpoint)) { + return 0; + } + int convertRate; + if (IntervalUnit.HOUR.getName().equals(endpoint.timeUnit)) { + convertRate = 60 * 60; + } else if (IntervalUnit.MINUTE.getName().equals(endpoint.timeUnit)) { + convertRate = 60; + } else convertRate = 1; + return Objects.nonNull(endpoint.timeUnitValue) + ? endpoint.coeff * endpoint.timeUnitValue * convertRate + : 0; + } public static boolean compare(Criteria c1, Criteria c2) {