From 665a097910cb18ed8b0937942423f4ec15cb9548 Mon Sep 17 00:00:00 2001 From: Sergey Suvorov Date: Fri, 23 Sep 2022 15:58:42 +0300 Subject: [PATCH] Added visit detail entity (#174) Co-authored-by: Pavel Grafkin Co-authored-by: Vitaly Koulakov Co-authored-by: Chris Knoll --- pom.xml | 8 +- .../java/org/ohdsi/circe/check/Checker.java | 2 + .../java/org/ohdsi/circe/check/Constants.java | 7 + .../circe/check/checkers/Comparisons.java | 23 +- .../check/checkers/ConceptCheckerFactory.java | 17 +- .../checkers/ConceptSetCriteriaCheck.java | 19 +- .../checkers/ConceptSetSelectionCheck.java | 49 ++ .../ConceptSetSelectionCheckerFactory.java | 85 +++ .../checkers/CriteriaCheckerFactory.java | 47 +- .../circe/check/checkers/DomainTypeCheck.java | 16 +- .../circe/check/checkers/DrugDomainCheck.java | 18 +- .../checkers/DuplicatesCriteriaCheck.java | 22 +- .../checkers/FirstTimeInHistoryCheck.java | 19 +- .../check/checkers/RangeCheckerFactory.java | 30 +- .../circe/check/utils/CriteriaNameHelper.java | 16 +- .../CohortExpressionQueryBuilder.java | 25 +- .../cohortdefinition/ConceptSetSelection.java | 33 ++ .../circe/cohortdefinition/Criteria.java | 1 + .../IGetCriteriaSqlDispatcher.java | 1 + .../circe/cohortdefinition/VisitDetail.java | 81 +++ .../builders/BuilderUtils.java | 2 +- .../builders/CriteriaColumn.java | 3 +- .../builders/VisitDetailSqlBuilder.java | 164 ++++++ .../printfriendly/criteriaTypes.ftl | 21 + .../printfriendly/inputTypes.ftl | 3 + .../cohortdefinition/sql/visitDetail.sql | 12 + .../org/ohdsi/circe/AbstractDatabaseTest.java | 2 +- .../ohdsi/circe/PostgresSingletonRule.java | 29 +- .../circe/check/checkers/ComparisonsTest.java | 28 +- .../checkers/ConceptSetCriteriaCheckTest.java | 2 +- .../checkers/CriteriaCheckValueTest.java | 2 +- .../check/checkers/DomainTypeCheckTest.java | 2 +- .../checkers/DuplicatesCriteriaCheckTest.java | 2 +- .../CohortGeneration_5_0_0_Test.java | 2 +- .../CorelatedCriteria_5_3_0_Test.java | 186 ++++++ .../builders/CriteriaColumn_5_0_0_Test.java | 9 + .../builders/CriteriaColumn_5_3_0_Test.java | 40 ++ .../builders/CriteriaUtils.java | 8 + .../builders/WindowCriteria_5_0_0_Test.java | 1 - .../builders/WindowCriteria_5_3_0_Test.java | 117 ++++ .../printfriendly/PrintFriendlyTest.java | 22 + .../conceptSetCriteriaCheckCorrect.json | 5 + .../conceptSetCriteriaCheckIncorrect.json | 3 + .../checkers/domainTypeCheckCorrect.json | 8 + .../checkers/domainTypeCheckIncorrect.json | 5 + .../checkers/drugDomainCheckCorrect.json | 5 + .../checkers/drugDomainCheckIncorrect.json | 5 + .../duplicatesCriteriaCheckCorrect.json | 5 + .../duplicatesCriteriaCheckIncorrect.json | 25 + .../primaryCriteriaCheckValueCorrect.json | 42 ++ .../primaryCriteriaCheckValueIncorrect.json | 43 ++ .../corelatedcriteria/distinctVisit_PREP.json | 10 +- .../distinctVisit_VERIFY.json | 2 +- .../visitDetailCodesetCriteria_PREP.json | 112 ++++ .../visitDetailCodesetCriteria_VERIFY.json | 29 + src/test/resources/ddl/cdm_v5.0.sql | 1 - src/test/resources/ddl/cdm_v5.3.sql | 542 ++++++++++++++++++ src/test/resources/ddl/resultsSchema.sql | 5 + .../printfriendly/nullCodesetId.json | 29 + .../resources/printfriendly/visitDetail.json | 115 ++++ .../windowVisitDetail_PREP.json | 117 ++++ .../windowVisitDetail_VERIFY.json | 54 ++ 62 files changed, 2117 insertions(+), 221 deletions(-) create mode 100644 src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheck.java create mode 100644 src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheckerFactory.java create mode 100644 src/main/java/org/ohdsi/circe/cohortdefinition/ConceptSetSelection.java create mode 100644 src/main/java/org/ohdsi/circe/cohortdefinition/VisitDetail.java create mode 100644 src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java create mode 100644 src/main/resources/resources/cohortdefinition/sql/visitDetail.sql create mode 100644 src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_3_0_Test.java create mode 100644 src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_3_0_Test.java create mode 100644 src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_3_0_Test.java create mode 100644 src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_PREP.json create mode 100644 src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_VERIFY.json create mode 100644 src/test/resources/ddl/cdm_v5.3.sql create mode 100644 src/test/resources/printfriendly/visitDetail.json create mode 100644 src/test/resources/windowcriteria/windowVisitDetail_PREP.json create mode 100644 src/test/resources/windowcriteria/windowVisitDetail_VERIFY.json diff --git a/pom.xml b/pom.xml index ca4c3e37..464de25e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.ohdsi circe - 1.9.5-SNAPSHOT + 1.10.0-SNAPSHOT 1.8 1.8 @@ -77,17 +77,17 @@ ohdsi repo.ohdsi.org - http://repo.ohdsi.org:8085/nexus/content/repositories/releases + https://repo.ohdsi.org/nexus/content/repositories/releases ohdsi.thirdparty repo.ohdsi.org - http://repo.ohdsi.org:8085/nexus/content/repositories/thirdparty + https://repo.ohdsi.org/nexus/content/repositories/thirdparty ohdsi.snapshots repo.ohdsi.org-snapshots - http://repo.ohdsi.org:8085/nexus/content/repositories/snapshots + https://repo.ohdsi.org/nexus/content/repositories/snapshots false diff --git a/src/main/java/org/ohdsi/circe/check/Checker.java b/src/main/java/org/ohdsi/circe/check/Checker.java index 9c2dcb31..cc7e1bea 100644 --- a/src/main/java/org/ohdsi/circe/check/Checker.java +++ b/src/main/java/org/ohdsi/circe/check/Checker.java @@ -21,6 +21,7 @@ import org.ohdsi.circe.check.checkers.AttributeCheck; import org.ohdsi.circe.check.checkers.ConceptCheck; import org.ohdsi.circe.check.checkers.ConceptSetCriteriaCheck; +import org.ohdsi.circe.check.checkers.ConceptSetSelectionCheck; import org.ohdsi.circe.check.checkers.CriteriaContradictionsCheck; import org.ohdsi.circe.check.checkers.DeathTimeWindowCheck; import org.ohdsi.circe.check.checkers.DomainTypeCheck; @@ -55,6 +56,7 @@ private List getChecks() { checks.add(new ExitCriteriaDaysOffsetCheck()); checks.add(new RangeCheck()); checks.add(new ConceptCheck()); + checks.add(new ConceptSetSelectionCheck()); checks.add(new AttributeCheck()); checks.add(new TextCheck()); checks.add(new IncompleteRuleCheck()); diff --git a/src/main/java/org/ohdsi/circe/check/Constants.java b/src/main/java/org/ohdsi/circe/check/Constants.java index 9824fdcc..fc1e5f94 100644 --- a/src/main/java/org/ohdsi/circe/check/Constants.java +++ b/src/main/java/org/ohdsi/circe/check/Constants.java @@ -34,6 +34,7 @@ interface Criteria { String PROCEDURE_OCCURRENCE = "procedure occurrence"; String SPECIMEN = "specimen"; String VISIT_OCCURRENCE = "visit occurrence"; + String VISIT_DETAIL = "visit detail"; String PAYER_PLAN_PERIOD = "payer plan period"; String OBSERVATION_PERIOD = "observation period"; String LOCATION_REGION = "location region"; @@ -94,6 +95,8 @@ interface Attributes { String ANATOMIC_SITE_ATTR = "anatomic site"; String DISEASE_STATUS_ATTR = "disease status"; String PLACE_OF_SERVICE_ATTR = "place of service"; + String ADMITTED_FROM_ATTR = "admitted from"; + String DISCHARGED_TO_ATTR = "discharged to"; String LOCATION_REGION_START_DATE_ATTR = "location region start date"; String LOCATION_REGION_END_DATE_ATTR = "location region end date"; String STOP_REASON_ATTR = "stop reason"; @@ -101,5 +104,9 @@ interface Attributes { String LOT_NUMBER_ATTR = "lot number"; String VALUE_AS_STRING_ATTR = "value as string"; String SOURCE_ID_ATTR = "source id"; + String VISIT_DETAIL_START_DATE_ATTR = "visit detail start date"; + String VISIT_DETAIL_END_DATE_ATTR = "visit detail end date"; + String VISIT_DETAIL_LENGTH_ATTR = "visit detail length"; + String VISIT_DETAIL_TYPE_ATTR = "visit detail type"; } } 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 ea245462..bf2404e3 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/Comparisons.java @@ -26,25 +26,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.builder.EqualsBuilder; -import org.ohdsi.circe.cohortdefinition.ConceptSet; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.DateRange; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.NumericRange; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationFilter; -import org.ohdsi.circe.cohortdefinition.Period; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; -import org.ohdsi.circe.cohortdefinition.Window; +import org.ohdsi.circe.cohortdefinition.*; import org.ohdsi.circe.vocabulary.Concept; public class Comparisons { @@ -173,6 +155,9 @@ public static boolean compare(Criteria c1, Criteria c2) { } else if (c1 instanceof VisitOccurrence) { VisitOccurrence vo1 = (VisitOccurrence) c1, vo2 = (VisitOccurrence) c2; result = Objects.equals(vo1.codesetId, vo2.codesetId); + } else if (c1 instanceof VisitDetail) { + VisitDetail vd1 = (VisitDetail) c1, vd2 = (VisitDetail) c2; + result = Objects.equals(vd1.codesetId, vd2.codesetId); } } return result; diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ConceptCheckerFactory.java b/src/main/java/org/ohdsi/circe/check/checkers/ConceptCheckerFactory.java index d2308b02..17a40723 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ConceptCheckerFactory.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ConceptCheckerFactory.java @@ -19,22 +19,7 @@ package org.ohdsi.circe.check.checkers; import org.ohdsi.circe.check.Constants; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DemographicCriteria; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationPeriod; -import org.ohdsi.circe.cohortdefinition.PayerPlanPeriod; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; import org.ohdsi.circe.vocabulary.Concept; import java.util.Objects; diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheck.java index 15e66d57..714c80c3 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheck.java @@ -24,19 +24,7 @@ import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.check.operations.Execution; import org.ohdsi.circe.check.utils.CriteriaNameHelper; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; public class ConceptSetCriteriaCheck extends BaseCriteriaCheck { @@ -109,6 +97,11 @@ protected void checkCriteria(Criteria criteria, String groupName, WarningReporte .then(c -> match((VisitOccurrence)c) .when(visitOccurrence -> Objects.isNull(visitOccurrence.codesetId) && Objects.isNull(visitOccurrence.visitSourceConcept)) + .then(addWarning)) + .isA(VisitDetail.class) + .then(c -> match((VisitDetail)c) + .when(visitDetail -> Objects.isNull(visitDetail.codesetId) + && Objects.isNull(visitDetail.visitDetailSourceConcept)) .then(addWarning)); } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheck.java new file mode 100644 index 00000000..4b83d0d7 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheck.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Observational Health Data Sciences and Informatics + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: Vitaly Koulakov + * + */ + +package org.ohdsi.circe.check.checkers; + +import org.ohdsi.circe.check.WarningSeverity; +import org.ohdsi.circe.check.operations.Execution; +import org.ohdsi.circe.check.utils.CriteriaNameHelper; +import org.ohdsi.circe.cohortdefinition.ConditionEra; +import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; +import org.ohdsi.circe.cohortdefinition.Criteria; +import org.ohdsi.circe.cohortdefinition.Death; +import org.ohdsi.circe.cohortdefinition.DeviceExposure; +import org.ohdsi.circe.cohortdefinition.DoseEra; +import org.ohdsi.circe.cohortdefinition.DrugEra; +import org.ohdsi.circe.cohortdefinition.DrugExposure; +import org.ohdsi.circe.cohortdefinition.Measurement; +import org.ohdsi.circe.cohortdefinition.Observation; +import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; +import org.ohdsi.circe.cohortdefinition.Specimen; +import org.ohdsi.circe.cohortdefinition.VisitDetail; +import org.ohdsi.circe.cohortdefinition.VisitOccurrence; + +import java.util.Objects; +import java.util.function.Function; + +import static org.ohdsi.circe.check.operations.Operations.match; + +public class ConceptSetSelectionCheck extends BaseValueCheck { + @Override + protected BaseCheckerFactory getFactory(WarningReporter reporter, String name) { + return ConceptSetSelectionCheckerFactory.getFactory(reporter, name); + } +} diff --git a/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheckerFactory.java b/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheckerFactory.java new file mode 100644 index 00000000..49cb8127 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/check/checkers/ConceptSetSelectionCheckerFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Observational Health Data Sciences and Informatics + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: Sergey Suvorov + * + */ + +package org.ohdsi.circe.check.checkers; + +import org.ohdsi.circe.check.Constants; +import org.ohdsi.circe.cohortdefinition.ConceptSetSelection; +import org.ohdsi.circe.cohortdefinition.ConditionEra; +import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; +import org.ohdsi.circe.cohortdefinition.Criteria; +import org.ohdsi.circe.cohortdefinition.Death; +import org.ohdsi.circe.cohortdefinition.DemographicCriteria; +import org.ohdsi.circe.cohortdefinition.DeviceExposure; +import org.ohdsi.circe.cohortdefinition.DoseEra; +import org.ohdsi.circe.cohortdefinition.DrugEra; +import org.ohdsi.circe.cohortdefinition.DrugExposure; +import org.ohdsi.circe.cohortdefinition.Measurement; +import org.ohdsi.circe.cohortdefinition.Observation; +import org.ohdsi.circe.cohortdefinition.ObservationPeriod; +import org.ohdsi.circe.cohortdefinition.PayerPlanPeriod; +import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; +import org.ohdsi.circe.cohortdefinition.Specimen; +import org.ohdsi.circe.cohortdefinition.VisitDetail; +import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.vocabulary.Concept; + +import java.util.Objects; +import java.util.function.Consumer; + +import static org.ohdsi.circe.check.Constants.Attributes.UNIT_ATTR; +import static org.ohdsi.circe.check.operations.Operations.match; + +public class ConceptSetSelectionCheckerFactory extends BaseCheckerFactory{ + private static final String WARNING_EMPTY_VALUE = "%s in the %s has empty %s value"; + + private ConceptSetSelectionCheckerFactory(WarningReporter reporter, String groupName) { + super(reporter, groupName); + } + + public static ConceptSetSelectionCheckerFactory getFactory(WarningReporter reporter, String groupName) { + return new ConceptSetSelectionCheckerFactory(reporter, groupName); + } + + @Override + protected Consumer getCheck(Criteria criteria) { + Consumer result = c -> { }; + if (criteria instanceof VisitDetail) { + result = c -> { + VisitDetail vd = (VisitDetail) c; + checkConcept(vd.visitDetailTypeCS, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.VISIT_DETAIL_TYPE_ATTR); + checkConcept(vd.genderCS, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.GENDER_ATTR); + checkConcept(vd.providerSpecialtyCS, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.PROVIDER_SPECIALITY_ATTR); + checkConcept(vd.placeOfServiceCS, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.PLACE_OF_SERVICE_ATTR); + }; + } + return result; + } + + @Override + protected Consumer getCheck(DemographicCriteria criteria) { + return c -> {}; + } + + private void checkConcept(ConceptSetSelection conceptSetSelection, String criteriaName, String attribute) { + Consumer warning = (t) -> reporter.add(t, groupName, criteriaName, attribute); + match(conceptSetSelection) + .when(r -> Objects.nonNull(r) && Objects.isNull(r.codesetId)) + .then(() -> warning.accept(WARNING_EMPTY_VALUE)); + } +} diff --git a/src/main/java/org/ohdsi/circe/check/checkers/CriteriaCheckerFactory.java b/src/main/java/org/ohdsi/circe/check/checkers/CriteriaCheckerFactory.java index 89dd197d..8b1ef025 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/CriteriaCheckerFactory.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/CriteriaCheckerFactory.java @@ -18,23 +18,15 @@ package org.ohdsi.circe.check.checkers; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Function; -import org.ohdsi.circe.cohortdefinition.ConceptSet; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.LocationRegion; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import java.util.function.Supplier; + +import org.ohdsi.circe.cohortdefinition.*; class CriteriaCheckerFactory { @@ -76,9 +68,34 @@ Function getCriteriaChecker(Criteria criteria) { result = c -> Objects.equals(((Specimen)c).codesetId, conceptSet.id); } else if (criteria instanceof VisitOccurrence) { result = c -> Objects.equals(((VisitOccurrence) c).codesetId, conceptSet.id); + } else if (criteria instanceof VisitDetail) { + result = c -> Objects.equals(((VisitDetail) c).codesetId, conceptSet.id) || checkConceptSetSelection(c); } else if (criteria instanceof LocationRegion) { result = c -> Objects.equals(((LocationRegion) c).codesetId, conceptSet.id); } return result; } + + private boolean checkConceptSetSelection(Criteria criteria) { + boolean result = false; + for (Supplier supplier : getSuppliers(criteria)) { + ConceptSetSelection conceptSetSelection = supplier.get(); + if (Objects.nonNull(conceptSetSelection)) { + result = result || Objects.equals(conceptSetSelection.codesetId, conceptSet.id); + } + } + return result; + } + + private static List> getSuppliers(T criteria) { + List> suppliers = new ArrayList<>(); + if (criteria instanceof VisitDetail) { + suppliers.add(() -> ((VisitDetail) criteria).placeOfServiceCS); + suppliers.add(() -> ((VisitDetail) criteria).genderCS); + suppliers.add(() -> ((VisitDetail) criteria).providerSpecialtyCS); + suppliers.add(() -> ((VisitDetail) criteria).visitDetailTypeCS); + } + + return suppliers; + } } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/DomainTypeCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/DomainTypeCheck.java index 19fcaf56..2391b2e8 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/DomainTypeCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/DomainTypeCheck.java @@ -27,17 +27,7 @@ import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.check.operations.Execution; import org.ohdsi.circe.check.utils.CriteriaNameHelper; -import org.ohdsi.circe.cohortdefinition.CohortExpression; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; public class DomainTypeCheck extends BaseCriteriaCheck { @@ -91,6 +81,10 @@ protected void checkCriteria(Criteria criteria, String groupName, WarningReporte .isA(VisitOccurrence.class) .then(c -> match((VisitOccurrence)c) .when(visitOccurrence -> Objects.isNull(visitOccurrence.visitType)) + .then(addWarning)) + .isA(VisitDetail.class) + .then(c -> match((VisitDetail)c) + .when(visitDetail -> Objects.isNull(visitDetail.visitDetailTypeCS)) .then(addWarning)); } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/DrugDomainCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/DrugDomainCheck.java index 1ac9bc2e..97f208c0 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/DrugDomainCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/DrugDomainCheck.java @@ -26,22 +26,7 @@ import java.util.stream.Collectors; import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.check.operations.Operations; -import org.ohdsi.circe.cohortdefinition.CohortExpression; -import org.ohdsi.circe.cohortdefinition.ConceptSet; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.CustomEraStrategy; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; public class DrugDomainCheck extends BaseCheck { @@ -93,6 +78,7 @@ private Integer mapCriteria(Criteria criteria) { .isA(ProcedureOccurrence.class).thenReturn(c -> ((ProcedureOccurrence)c).codesetId) .isA(Specimen.class).thenReturn(c -> ((Specimen)c).codesetId) .isA(VisitOccurrence.class).thenReturn(c -> ((VisitOccurrence)c).codesetId) + .isA(VisitDetail.class).thenReturn(c -> ((VisitDetail)c).codesetId) .value(); } diff --git a/src/main/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheck.java b/src/main/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheck.java index 02c5ea87..6e23bcd3 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheck.java @@ -28,22 +28,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.check.utils.CriteriaNameHelper; -import org.ohdsi.circe.cohortdefinition.CohortExpression; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationPeriod; -import org.ohdsi.circe.cohortdefinition.PayerPlanPeriod; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; public class DuplicatesCriteriaCheck extends BaseCriteriaCheck { @@ -144,6 +129,11 @@ private boolean compareCriteria(Criteria c1, Criteria c2) { return new EqualsBuilder() .append(vo1.codesetId, vo2.codesetId) .build(); + } else if (c1 instanceof VisitDetail) { + VisitDetail vd1 = (VisitDetail) c1, vd2 = (VisitDetail) c2; + return new EqualsBuilder() + .append(vd1.codesetId, vd2.codesetId) + .build(); } else if (c1 instanceof PayerPlanPeriod) { PayerPlanPeriod p1 = (PayerPlanPeriod) c1, p2 = (PayerPlanPeriod) c2; return new EqualsBuilder() 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 52b467ab..8280bcd7 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/FirstTimeInHistoryCheck.java @@ -24,20 +24,7 @@ import org.ohdsi.circe.check.WarningSeverity; import org.ohdsi.circe.check.operations.Execution; import org.ohdsi.circe.check.utils.CriteriaNameHelper; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.CorelatedCriteria; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationPeriod; -import org.ohdsi.circe.cohortdefinition.PayerPlanPeriod; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; public class FirstTimeInHistoryCheck extends BaseCorelatedCriteriaCheck { @@ -107,6 +94,10 @@ protected void checkCriteria(CorelatedCriteria criteria, String groupName, Warni .then(c -> match((VisitOccurrence)c) .when(visitOccurrence -> Objects.isNull(visitOccurrence.first)) .then(addWarning)) + .isA(VisitDetail.class) + .then(c -> match((VisitDetail)c) + .when(visitDetail -> Objects.isNull(visitDetail.first)) + .then(addWarning)) .isA(PayerPlanPeriod.class) .then(c -> match((PayerPlanPeriod)c) .when(payerPlanPeriod -> Objects.isNull(payerPlanPeriod.first)) diff --git a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheckerFactory.java b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheckerFactory.java index adcb831c..27232b62 100644 --- a/src/main/java/org/ohdsi/circe/check/checkers/RangeCheckerFactory.java +++ b/src/main/java/org/ohdsi/circe/check/checkers/RangeCheckerFactory.java @@ -19,27 +19,7 @@ package org.ohdsi.circe.check.checkers; import org.ohdsi.circe.check.Constants; -import org.ohdsi.circe.cohortdefinition.CohortExpression; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.DateRange; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DemographicCriteria; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.LocationRegion; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.NumericRange; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationPeriod; -import org.ohdsi.circe.cohortdefinition.PayerPlanPeriod; -import org.ohdsi.circe.cohortdefinition.Period; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; import java.util.Objects; import java.util.function.Consumer; @@ -181,6 +161,14 @@ protected Consumer getCheck(Criteria criteria) { checkRange(vo.visitLength, Constants.Criteria.VISIT_OCCURRENCE, Constants.Attributes.VISIT_LENGTH_ATTR); checkRange(vo.age, Constants.Criteria.VISIT_OCCURRENCE, Constants.Attributes.AGE_ATTR); }; + } else if (criteria instanceof VisitDetail) { + result = c -> { + VisitDetail vd = (VisitDetail) c; + checkRange(vd.visitDetailStartDate, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.VISIT_DETAIL_START_DATE_ATTR); + checkRange(vd.visitDetailEndDate, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.VISIT_DETAIL_END_DATE_ATTR); + checkRange(vd.visitDetailLength, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.VISIT_DETAIL_LENGTH_ATTR); + checkRange(vd.age, Constants.Criteria.VISIT_DETAIL, Constants.Attributes.AGE_ATTR); + }; } else if (criteria instanceof PayerPlanPeriod) { result = c -> { PayerPlanPeriod planPeriod = (PayerPlanPeriod) c; diff --git a/src/main/java/org/ohdsi/circe/check/utils/CriteriaNameHelper.java b/src/main/java/org/ohdsi/circe/check/utils/CriteriaNameHelper.java index fc4f8b87..ef639ddc 100644 --- a/src/main/java/org/ohdsi/circe/check/utils/CriteriaNameHelper.java +++ b/src/main/java/org/ohdsi/circe/check/utils/CriteriaNameHelper.java @@ -20,20 +20,7 @@ import org.ohdsi.circe.check.Constants; import org.ohdsi.circe.check.operations.Operations; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationPeriod; -import org.ohdsi.circe.cohortdefinition.PayerPlanPeriod; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; +import org.ohdsi.circe.cohortdefinition.*; public class CriteriaNameHelper { @@ -53,6 +40,7 @@ public static String getCriteriaName(org.ohdsi.circe.cohortdefinition.Criteria c .isA(ProcedureOccurrence.class).thenReturn(c -> Constants.Criteria.PROCEDURE_OCCURRENCE) .isA(Specimen.class).thenReturn(c -> Constants.Criteria.SPECIMEN) .isA(VisitOccurrence.class).thenReturn(c -> Constants.Criteria.VISIT_OCCURRENCE) + .isA(VisitDetail.class).thenReturn(c -> Constants.Criteria.VISIT_DETAIL) .isA(PayerPlanPeriod.class).thenReturn(c -> Constants.Criteria.PAYER_PLAN_PERIOD) .value(); } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java index 412a35c0..4bf579e7 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/CohortExpressionQueryBuilder.java @@ -26,23 +26,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang3.StringUtils; -import org.ohdsi.circe.cohortdefinition.builders.BuilderOptions; -import org.ohdsi.circe.cohortdefinition.builders.CriteriaSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.ConditionEraSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.ConditionOccurrenceSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.DeathSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.DeviceExposureSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.DoseEraSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.DrugEraSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.DrugExposureSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.LocationRegionSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.MeasurementSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.ObservationPeriodSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.ObservationSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.PayerPlanPeriodSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.ProcedureOccurrenceSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.SpecimenSqlBuilder; -import org.ohdsi.circe.cohortdefinition.builders.VisitOccurrenceSqlBuilder; +import org.ohdsi.circe.cohortdefinition.builders.*; import org.ohdsi.circe.helper.ResourceHelper; import org.ohdsi.circe.vocabulary.ConceptSetExpressionQueryBuilder; @@ -50,7 +34,6 @@ import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.buildNumericRangeClause; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.dateStringToSql; import static org.ohdsi.circe.cohortdefinition.builders.BuilderUtils.getConceptIdsFromConcepts; -import org.ohdsi.circe.cohortdefinition.builders.CriteriaColumn; /** * @@ -102,6 +85,7 @@ public class CohortExpressionQueryBuilder implements IGetCriteriaSqlDispatcher, private final static ProcedureOccurrenceSqlBuilder procedureOccurrenceSqlBuilder = new ProcedureOccurrenceSqlBuilder<>(); private final static SpecimenSqlBuilder specimenSqlBuilder = new SpecimenSqlBuilder<>(); private final static VisitOccurrenceSqlBuilder visitOccurrenceSqlBuilder = new VisitOccurrenceSqlBuilder<>(); + private final static VisitDetailSqlBuilder visitDetailSqlBuilder = new VisitDetailSqlBuilder<>(); private final static ConditionEraSqlBuilder conditionEraSqlBuilder = new ConditionEraSqlBuilder<>(); private final static String DEFAULT_COHORT_ID_FIELD_NAME = "cohort_definition_id"; @@ -755,6 +739,11 @@ public String getCriteriaSql(VisitOccurrence criteria, BuilderOptions options) { return getCriteriaSql(visitOccurrenceSqlBuilder, criteria, options); } + @Override + public String getCriteriaSql(VisitDetail criteria, BuilderOptions options) { + return getCriteriaSql(visitDetailSqlBuilder, criteria, options); + } + @Override public String getCriteriaSql(LocationRegion criteria, BuilderOptions options) { return getCriteriaSql(locationRegionSqlBuilder, criteria, options); diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/ConceptSetSelection.java b/src/main/java/org/ohdsi/circe/cohortdefinition/ConceptSetSelection.java new file mode 100644 index 00000000..e5e9e4fe --- /dev/null +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/ConceptSetSelection.java @@ -0,0 +1,33 @@ +/* + * + * Copyright 2017 Observational Health Data Sciences and Informatics + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: Christopher Knoll + * + */ +package org.ohdsi.circe.cohortdefinition; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + * @author cknoll1 + */ +public class ConceptSetSelection { + @JsonProperty("CodesetId") + public Integer codesetId; + + @JsonProperty("IsExclusion") + public Boolean isExclusion; +} diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java b/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java index 4cfbd6d0..336e8f0f 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/Criteria.java @@ -45,6 +45,7 @@ @JsonSubTypes.Type(value = ProcedureOccurrence.class, name = "ProcedureOccurrence"), @JsonSubTypes.Type(value = Specimen.class, name = "Specimen"), @JsonSubTypes.Type(value = VisitOccurrence.class, name = "VisitOccurrence"), + @JsonSubTypes.Type(value = VisitDetail.class, name = "VisitDetail"), @JsonSubTypes.Type(value = PayerPlanPeriod.class, name = "PayerPlanPeriod") }) public abstract class Criteria { diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/IGetCriteriaSqlDispatcher.java b/src/main/java/org/ohdsi/circe/cohortdefinition/IGetCriteriaSqlDispatcher.java index b33123e2..46c6bd6a 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/IGetCriteriaSqlDispatcher.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/IGetCriteriaSqlDispatcher.java @@ -40,4 +40,5 @@ public interface IGetCriteriaSqlDispatcher { String getCriteriaSql(ProcedureOccurrence procedureOccurrenceCriteria, BuilderOptions options); String getCriteriaSql(Specimen specimenCriteria, BuilderOptions options); String getCriteriaSql(VisitOccurrence visitOccurrenceCriteria, BuilderOptions options); + String getCriteriaSql(VisitDetail visitDetailCriteria, BuilderOptions options); } diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/VisitDetail.java b/src/main/java/org/ohdsi/circe/cohortdefinition/VisitDetail.java new file mode 100644 index 00000000..3b7859c2 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/VisitDetail.java @@ -0,0 +1,81 @@ +/* + * + * Copyright 2017 Observational Health Data Sciences and Informatics + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: Christopher Knoll + * + */ +package org.ohdsi.circe.cohortdefinition; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.ohdsi.analysis.versioning.CdmVersion; +import org.ohdsi.circe.cohortdefinition.builders.BuilderOptions; +import org.ohdsi.circe.vocabulary.Concept; + +/** + * + * @author cknoll1 + */ +public class VisitDetail extends Criteria { + + @JsonProperty("CodesetId") + public Integer codesetId; + + @JsonProperty("First") + public Boolean first; + + @JsonProperty("VisitDetailStartDate") + public DateRange visitDetailStartDate; + + @JsonProperty("VisitDetailEndDate") + public DateRange visitDetailEndDate; + + @JsonProperty("VisitDetailTypeCS") + public ConceptSetSelection visitDetailTypeCS; + + @JsonProperty("VisitDetailSourceConcept") + public Integer visitDetailSourceConcept; + + @JsonProperty("VisitDetailLength") + public NumericRange visitDetailLength; + + @JsonProperty("Age") + public NumericRange age; + + @JsonProperty("GenderCS") + public ConceptSetSelection genderCS; + + @JsonProperty("ProviderSpecialtyCS") + public ConceptSetSelection providerSpecialtyCS; + + @JsonProperty("PlaceOfServiceCS") + public ConceptSetSelection placeOfServiceCS; + + /** + * ID of Codeset which defines Geo concepts. + * The care site's location.region_concept_id should match one of those. + */ + + @CdmVersion(range = ">=6.1") + @JsonProperty("PlaceOfServiceLocation") + public Integer placeOfServiceLocation; + + @Override + public String accept(IGetCriteriaSqlDispatcher dispatcher, BuilderOptions options) + { + return dispatcher.getCriteriaSql(this, options); + } + + +} diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderUtils.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderUtils.java index 47f57ec9..a6691b25 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderUtils.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/BuilderUtils.java @@ -13,7 +13,7 @@ public abstract class BuilderUtils { private final static String CODESET_JOIN_TEMPLATE = "JOIN #Codesets %s on (%s = %s.concept_id and %s.codeset_id = %d)"; private final static String STANARD_ALIAS = "cs"; private final static String NON_STANARD_ALIAS = "cns"; - + public static String getCodesetJoinExpression(Integer standardCodesetId, String standardConceptColumn, Integer sourceCodesetId, String sourceConceptColumn) { String joinExpression = ""; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn.java index 858ea33b..aa06f369 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn.java @@ -34,7 +34,8 @@ public enum CriteriaColumn { START_DATE("start_date"), UNIT("unit_concept_id"), VALUE_AS_NUMBER("value_as_number"), - VISIT_ID("visit_occurrence_id"); + VISIT_ID("visit_occurrence_id"), + VISIT_DETAIL_ID("visit_detail_id"); private final String columnName; diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java new file mode 100644 index 00000000..23d06f50 --- /dev/null +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/VisitDetailSqlBuilder.java @@ -0,0 +1,164 @@ +package org.ohdsi.circe.cohortdefinition.builders; + +import org.apache.commons.lang3.StringUtils; +import org.ohdsi.circe.cohortdefinition.ConceptSetSelection; +import org.ohdsi.circe.cohortdefinition.VisitDetail; +import org.ohdsi.circe.helper.ResourceHelper; + +import java.util.*; + +public class VisitDetailSqlBuilder extends CriteriaSqlBuilder { + + private final static String VISIT_DETAIL_TEMPLATE = ResourceHelper.GetResourceAsString("/resources/cohortdefinition/sql/visitDetail.sql"); + + // default columns are those that are specified in the template, and dont' need to be added if specifeid in 'additionalColumns' + private final Set DEFAULT_COLUMNS = new HashSet<>(Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.VISIT_DETAIL_ID)); + + @Override + protected Set getDefaultColumns() { + return DEFAULT_COLUMNS; + } + + @Override + protected String getQueryTemplate() { + return VISIT_DETAIL_TEMPLATE; + } + + @Override + protected String getTableColumnForCriteriaColumn(CriteriaColumn column) { + switch (column) { + case DOMAIN_CONCEPT: + return "C.visit_detail_concept_id"; + case DURATION: + return "DATEDIFF(d, C.visit_detail_start_date, C.visit_detail_end_date)"; + default: + throw new IllegalArgumentException("Invalid CriteriaColumn for Visit Detail:" + column.toString()); + } + } + + @Override + protected String embedCodesetClause(String query, T criteria) { + + return StringUtils.replace(query, "@codesetClause", + BuilderUtils.getCodesetJoinExpression(criteria.codesetId, + "vd.visit_detail_concept_id", + criteria.visitDetailSourceConcept, + "vd.visit_detail_source_concept_id") + ); + } + + @Override + protected String embedOrdinalExpression(String query, T criteria, List whereClauses) { + // first + if (criteria.first != null && criteria.first == true) { + whereClauses.add("C.ordinal = 1"); + query = StringUtils.replace(query, "@ordinalExpression", ", row_number() over (PARTITION BY vd.person_id ORDER BY vd.visit_detail_start_date, vd.visit_detail_id) as ordinal"); + } else { + query = StringUtils.replace(query, "@ordinalExpression", ""); + } + return query; + } + + @Override + protected List resolveJoinClauses(T criteria) { + + List joinClauses = new ArrayList<>(); + + if (criteria.age != null || criteria.genderCS != null) // join to PERSON + { + joinClauses.add("JOIN @cdm_database_schema.PERSON P on C.person_id = P.person_id"); + } + if (criteria.placeOfServiceCS != null || criteria.placeOfServiceLocation != null) { + joinClauses.add("JOIN @cdm_database_schema.CARE_SITE CS on C.care_site_id = CS.care_site_id"); + } + if (criteria.providerSpecialtyCS != null) { + joinClauses.add("LEFT JOIN @cdm_database_schema.PROVIDER PR on C.provider_id = PR.provider_id"); + } + + if (criteria.placeOfServiceLocation != null) { + addFilteringByCareSiteLocationRegion(joinClauses, criteria.placeOfServiceLocation); + } + + return joinClauses; + } + + @Override + protected List resolveWhereClauses(T criteria) { + + List whereClauses = new ArrayList<>(); + + // occurrenceStartDate + if (criteria.visitDetailStartDate != null) { + whereClauses.add(BuilderUtils.buildDateRangeClause("C.visit_detail_start_date", criteria.visitDetailStartDate)); + } + + // occurrenceEndDate + if (criteria.visitDetailEndDate != null) { + whereClauses.add(BuilderUtils.buildDateRangeClause("C.visit_detail_end_date", criteria.visitDetailEndDate)); + } + + // visitType + if (criteria.visitDetailTypeCS != null && criteria.visitDetailTypeCS.codesetId != null) { + addWhereClause(whereClauses, criteria.visitDetailTypeCS, "C.visit_detail_type_concept_id"); + } + + // visitLength + if (criteria.visitDetailLength != null) { + whereClauses.add(BuilderUtils.buildNumericRangeClause("DATEDIFF(d,C.visit_detail_start_date, C.visit_detail_end_date)", criteria.visitDetailLength)); + } + + // age + if (criteria.age != null) { + whereClauses.add(BuilderUtils.buildNumericRangeClause("YEAR(C.visit_detail_start_date) - P.year_of_birth", criteria.age)); + } + + // gender + if (criteria.genderCS != null && criteria.genderCS.codesetId != null) { + addWhereClause(whereClauses, criteria.genderCS, "P.gender_concept_id"); + } + + // providerSpecialty + if (criteria.providerSpecialtyCS != null && criteria.providerSpecialtyCS.codesetId != null) { + addWhereClause(whereClauses, criteria.providerSpecialtyCS, "PR.specialty_concept_id"); + } + + // placeOfService + if (criteria.placeOfServiceCS != null && criteria.placeOfServiceCS.codesetId != null) { + addWhereClause(whereClauses, criteria.placeOfServiceCS, "CS.place_of_service_concept_id"); + } + + return whereClauses; + } + + protected void addFilteringByCareSiteLocationRegion(List joinClauses, Integer codesetId) { + + joinClauses.add(getLocationHistoryJoin("LH", "CARE_SITE", "C.care_site_id")); + joinClauses.add("JOIN @cdm_database_schema.LOCATION LOC on LOC.location_id = LH.location_id"); + addFiltering(joinClauses, codesetId, "LOC.region_concept_id"); + } + + private void addWhereClause(List whereClauses, ConceptSetSelection conceptSetSelection, String conceptColumn) { + whereClauses.add(String.format("%s %s in (select concept_id from #Codesets where codeset_id = %s)", + conceptColumn, (conceptSetSelection.isExclusion ? "not" : ""), conceptSetSelection.codesetId)); + } + + private void addFiltering(List joinClauses, Integer codesetId, String standardConceptColumn) { + joinClauses.add( + BuilderUtils.getCodesetJoinExpression( + codesetId, + standardConceptColumn, + null, + null + ) + ); + } + + protected String getLocationHistoryJoin(String alias, String domain, String entityIdField) { + + return "JOIN @cdm_database_schema.LOCATION_HISTORY " + alias + " " + + "on " + alias + ".entity_id = " + entityIdField + " " + + "AND " + alias + ".domain_id = '" + domain + "' " + + "AND C.visit_detail_start_date >= " + alias + ".start_date " + + "AND C.visit_detail_end_date <= ISNULL(" + alias + ".end_date, DATEFROMPARTS(2099,12,31))"; + } +} diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl index 0012e9b4..ab1392a3 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/criteriaTypes.ftl @@ -27,6 +27,7 @@ END Note!!!! <#elseif c.class.simpleName == "ProcedureOccurrence"><@ProcedureOccurrence c=c level=level isPlural=isPlural countCriteria=countCriteria indexLabel=indexLabel /> <#elseif c.class.simpleName == "Specimen"><@Specimen c=c level=level isPlural=isPlural countCriteria=countCriteria indexLabel=indexLabel /> <#elseif c.class.simpleName == "VisitOccurrence"><@VisitOccurrence c=c level=level isPlural=isPlural countCriteria=countCriteria indexLabel=indexLabel /> +<#elseif c.class.simpleName == "VisitDetail"><@VisitDetail c=c level=level isPlural=isPlural countCriteria=countCriteria indexLabel=indexLabel /> <#else>Unknown criteria type: ${c.class.simpleName} <#macro ConditionEra c level isPlural=true countCriteria={} indexLabel="cohort entry"><#local attrs = []><#if countCriteria?has_content> @@ -215,6 +216,20 @@ c.visitSourceConcept??> (including ${utils.codesetName(c.visitSourceConcept, "an c.first!false> for the first time in the person's history<#if attrs?size gt 0>, ${attrs?join("; ")}<#if c.CorrelatedCriteria??>; <@Group group=c.CorrelatedCriteria level=level indexLabel=utils.codesetName(c.codesetId!"", "any visit") /><#else>. +<#macro VisitDetail c level isPlural=true countCriteria={} indexLabel="cohort entry"><#local attrs = []><#local attrs = []><#if countCriteria?has_content> +<#local temp><@WindowCriteria countCriteria=countCriteria indexLabel=indexLabel/><#if temp?has_content><#local attrs+=[temp]> +<#local temp><@AgeGenderCSCriteria ageAtStart=c.age!{} genderCS=c.genderCS!{} /><#if temp?has_content><#local attrs+=[temp]> +<#local temp><@EventDateCriteria c.visitDetailStartDate!{} c.visitDetailEndDate!{} /><#if temp?has_content><#local attrs+=[temp]><#if +c.visitDetailTypeCS?? && c.visitDetailTypeCS.codesetId??><#local temp>a visit detail type that is <@inputTypes.ConceptSetSelection selection=c.visitDetailTypeCS /> concept set<#local attrs+=[temp]><#if +c.providerSpecialtyCS?? && c.providerSpecialtyCS.codesetId??><#local temp>a provider specialty that is <@inputTypes.ConceptSetSelection selection=c.providerSpecialtyCS /> concept set<#local attrs+=[temp]><#if +c.placeOfServiceCS?? && c.placeOfServiceCS.codesetId??><#local temp>a place of service that is <@inputTypes.ConceptSetSelection selection=c.visitDetailTypeCS /> concept set<#local attrs+=[temp]><#if +c.visitDetailLength??><#local temp>with length <@inputTypes.NumericRange range=c.visitDetailLength /> days<#local attrs+=[temp]> +visit detail<#if isPlural && !(c.first!false)>s of ${utils.codesetName(c.codesetId!"", "any visit detail")}<#if +c.visitDetailSourceConcept??> (including ${utils.codesetName(c.visitDetailSourceConcept, "any visit")} source concepts)<#if +c.first!false> for the first time in the person's history<#if attrs?size gt 0>, ${attrs?join("; ")}<#if +c.CorrelatedCriteria??>; <@Group group=c.CorrelatedCriteria level=level indexLabel=utils.codesetName(c.codesetId!"", "any visit detail") /><#else>. + + <#-- temp has content: '${temp}' --> <#-- Criteria attribute templates --> @@ -225,6 +240,12 @@ who are<#if gender?has_content> <@inputTypes.ConceptList list=gender quote=""/>< ageAtStart?has_content> <@inputTypes.NumericRange range=ageAtStart /> years old<#if ageAtEnd?has_content> at era start and<#if ageAtEnd?has_content> <@inputTypes.NumericRange range=ageAtEnd /> years old at era end +<#macro AgeGenderCSCriteria ageAtStart genderCS ageAtEnd={}> +<#if ageAtStart?has_content || ageAtEnd?has_content>who are <#if +ageAtStart?has_content> <@inputTypes.NumericRange range=ageAtStart /> years old<#if ageAtEnd?has_content> at era start and <#if +ageAtEnd?has_content><@inputTypes.NumericRange range=ageAtEnd /> years old at era end<#if genderCS?has_content>, and <#if +genderCS?has_content>who have gender concept <@inputTypes.ConceptSetSelection selection=genderCS /> concept set + <#macro EventDateCriteria startRange endRange><#if startRange?has_content && endRange?has_content>starting <@inputTypes.DateRange range=startRange /> and ending <@inputTypes.DateRange range=endRange /><#else><#if startRange?has_content>starting <@inputTypes.DateRange range=startRange /><#if diff --git a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl index 8c3f43c3..00928c04 100644 --- a/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl +++ b/src/main/resources/resources/cohortdefinition/printfriendly/inputTypes.ftl @@ -28,6 +28,9 @@ END Note!!!! <#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": "<"}, diff --git a/src/main/resources/resources/cohortdefinition/sql/visitDetail.sql b/src/main/resources/resources/cohortdefinition/sql/visitDetail.sql new file mode 100644 index 00000000..0e342da0 --- /dev/null +++ b/src/main/resources/resources/cohortdefinition/sql/visitDetail.sql @@ -0,0 +1,12 @@ +-- Begin Visit Detail Criteria +select C.person_id, C.visit_detail_id as event_id, C.visit_detail_start_date as start_date, C.visit_detail_end_date as end_date, + C.visit_occurrence_id, C.visit_detail_start_date as sort_date@additionalColumns +from +( + select vd.* @ordinalExpression + FROM @cdm_database_schema.VISIT_DETAIL vd +@codesetClause +) C +@joinClause +@whereClause +-- End Visit Detail Criteria diff --git a/src/test/java/org/ohdsi/circe/AbstractDatabaseTest.java b/src/test/java/org/ohdsi/circe/AbstractDatabaseTest.java index 2ce81875..98f4726e 100644 --- a/src/test/java/org/ohdsi/circe/AbstractDatabaseTest.java +++ b/src/test/java/org/ohdsi/circe/AbstractDatabaseTest.java @@ -48,7 +48,7 @@ public class AbstractDatabaseTest { @ClassRule - public static PostgresSingletonRule pg = new PostgresSingletonRule(); + public static PostgresSingletonRule pg = new PostgresSingletonRule(58915); protected static JdbcTemplate jdbcTemplate; diff --git a/src/test/java/org/ohdsi/circe/PostgresSingletonRule.java b/src/test/java/org/ohdsi/circe/PostgresSingletonRule.java index a7d2b9c5..da2c8a76 100644 --- a/src/test/java/org/ohdsi/circe/PostgresSingletonRule.java +++ b/src/test/java/org/ohdsi/circe/PostgresSingletonRule.java @@ -34,21 +34,28 @@ import org.junit.rules.ExternalResource; import com.opentable.db.postgres.embedded.EmbeddedPostgres; +import com.opentable.db.postgres.embedded.EmbeddedPostgres.Builder; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Based off of com.opentable.db.postgres.junit.SingleInstancePostgresRule, but - * instantiates a single instance of an EmbeddedPostgres that cleans up when JVM - * shuts down. + * Based off of com.opentable.db.postgres.junit.SingleInstancePostgresRule, but instantiates a single instance of an + * EmbeddedPostgres that cleans up when JVM shuts down. */ public class PostgresSingletonRule extends ExternalResource { - private static volatile EmbeddedPostgres epg; - private static volatile Connection postgresConnection; + private volatile EmbeddedPostgres epg; + private volatile Connection postgresConnection; private static final Logger LOG = LoggerFactory.getLogger(PostgresSingletonRule.class); + private Optional port = Optional.empty(); - PostgresSingletonRule() {} + PostgresSingletonRule() { + } + + PostgresSingletonRule(int port) { + this.port = Optional.of(port); + } @Override protected void before() throws Throwable { @@ -58,13 +65,17 @@ protected void before() throws Throwable { LOG.info("Starting singleton Postgres instance..."); epg = pg(); postgresConnection = epg.getPostgresDatabase().getConnection(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown())); + Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown())); } } } private EmbeddedPostgres pg() throws IOException { - return EmbeddedPostgres.builder().start(); + Builder b = EmbeddedPostgres.builder(); + if (this.port.isPresent()) { + b.setPort(port.get()); + } + return b.start(); } public EmbeddedPostgres getEmbeddedPostgres() { @@ -75,7 +86,7 @@ public EmbeddedPostgres getEmbeddedPostgres() { return epg; } - private static void shutdown() { + private void shutdown() { LOG.info("Shutdown singleton Postgres instance..."); try { postgresConnection.close(); 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 edb11f52..a9edf413 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ComparisonsTest.java @@ -1,25 +1,7 @@ package org.ohdsi.circe.check.checkers; import org.junit.Test; -import org.ohdsi.circe.cohortdefinition.ConceptSet; -import org.ohdsi.circe.cohortdefinition.ConditionEra; -import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; -import org.ohdsi.circe.cohortdefinition.Criteria; -import org.ohdsi.circe.cohortdefinition.DateRange; -import org.ohdsi.circe.cohortdefinition.Death; -import org.ohdsi.circe.cohortdefinition.DeviceExposure; -import org.ohdsi.circe.cohortdefinition.DoseEra; -import org.ohdsi.circe.cohortdefinition.DrugEra; -import org.ohdsi.circe.cohortdefinition.DrugExposure; -import org.ohdsi.circe.cohortdefinition.Measurement; -import org.ohdsi.circe.cohortdefinition.NumericRange; -import org.ohdsi.circe.cohortdefinition.Observation; -import org.ohdsi.circe.cohortdefinition.ObservationFilter; -import org.ohdsi.circe.cohortdefinition.Period; -import org.ohdsi.circe.cohortdefinition.ProcedureOccurrence; -import org.ohdsi.circe.cohortdefinition.Specimen; -import org.ohdsi.circe.cohortdefinition.VisitOccurrence; -import org.ohdsi.circe.cohortdefinition.Window; +import org.ohdsi.circe.cohortdefinition.*; import org.ohdsi.circe.helper.ResourceHelper; import org.ohdsi.circe.vocabulary.Concept; import org.ohdsi.circe.vocabulary.ConceptSetExpression; @@ -247,6 +229,14 @@ public void compareCriteria() { assertEquals(true, Comparisons.compare(visitOccurrence1, visitOccurrence2)); visitOccurrence2.codesetId = 2; assertEquals(false, Comparisons.compare(visitOccurrence1, visitOccurrence2)); + + VisitDetail visitDetail1 = new VisitDetail(); + VisitDetail visitDetail2 = new VisitDetail(); + visitDetail1.codesetId = 1; + visitDetail2.codesetId = 1; + assertEquals(true, Comparisons.compare(visitDetail1, visitDetail2)); + visitDetail2.codesetId = 2; + assertEquals(false, Comparisons.compare(visitDetail1, visitDetail2)); } @Test diff --git a/src/test/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheckTest.java index b7ad6e13..113524f2 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/ConceptSetCriteriaCheckTest.java @@ -15,7 +15,7 @@ public class ConceptSetCriteriaCheckTest { private static final CohortExpression CORRECT_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/conceptSetCriteriaCheckCorrect.json")); - private static final int WARNING_COUNT = 12; + private static final int WARNING_COUNT = 13; private BaseCheck check = new ConceptSetCriteriaCheck(); 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 4bb69784..062ef9ae 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/CriteriaCheckValueTest.java @@ -54,7 +54,7 @@ public class CriteriaCheckValueTest { private static final CohortExpression NO_EXIT_CRITERIA_CHECK_EARLIEST_EVENT = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/noExitCriteriaCheckEarliestEvent.json")); - private static final int RANGE_PRIMARY_WARNING_COUNT = 140; + private static final int RANGE_PRIMARY_WARNING_COUNT = 148; private static final int CONCEPT_PRIMARY_WARNING_COUNT = 61; private static final int TEXT_PRIMARY_WARNING_COUNT = 5; diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DomainTypeCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DomainTypeCheckTest.java index 88351fbd..f7cd8b0b 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DomainTypeCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DomainTypeCheckTest.java @@ -15,7 +15,7 @@ public class DomainTypeCheckTest { private static final CohortExpression CORRECT_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/domainTypeCheckCorrect.json")); - private static final int WARNING_COUNT = 9; + private static final int WARNING_COUNT = 10; private BaseCheck check = new DomainTypeCheck(); diff --git a/src/test/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheckTest.java b/src/test/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheckTest.java index 8942fbf1..0304b375 100644 --- a/src/test/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheckTest.java +++ b/src/test/java/org/ohdsi/circe/check/checkers/DuplicatesCriteriaCheckTest.java @@ -15,7 +15,7 @@ public class DuplicatesCriteriaCheckTest { private static final CohortExpression CORRECT_EXPRESSION = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/checkers/duplicatesCriteriaCheckCorrect.json")); - private static final int WARNING_COUNT = 16; + private static final int WARNING_COUNT = 17; private BaseCheck check = new DuplicatesCriteriaCheck(); 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 a26fff46..e9b0f216 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 @@ -177,7 +177,7 @@ public void firstOccurrenceTest() throws Exception { new ProcedureOccurrence(), new Specimen(), new VisitOccurrence(), - new PayerPlanPeriod() + new PayerPlanPeriod() }; // create and execute cohort for each test criteria 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 new file mode 100644 index 00000000..c762aaa6 --- /dev/null +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CorelatedCriteria_5_3_0_Test.java @@ -0,0 +1,186 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.ohdsi.circe.cohortdefinition.builders; + +import com.github.mjeanroy.dbunit.core.dataset.DataSetFactory; +import org.dbunit.Assertion; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.IDataSet; +import org.dbunit.dataset.ITable; +import org.dbunit.operation.DatabaseOperation; +import org.junit.BeforeClass; +import org.junit.Test; +import org.ohdsi.circe.AbstractDatabaseTest; +import org.ohdsi.circe.cohortdefinition.CohortExpressionQueryBuilder; +import org.ohdsi.circe.cohortdefinition.ConceptSetSelection; +import org.ohdsi.circe.cohortdefinition.ConditionOccurrence; +import org.ohdsi.circe.cohortdefinition.CorelatedCriteria; +import org.ohdsi.circe.cohortdefinition.CriteriaGroup; +import org.ohdsi.circe.cohortdefinition.Occurrence; +import org.ohdsi.circe.cohortdefinition.VisitDetail; +import org.ohdsi.sql.SqlRender; +import org.ohdsi.sql.SqlTranslate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * + * @author cknoll1 + */ +public class CorelatedCriteria_5_3_0_Test extends AbstractDatabaseTest { + private final static Logger log = LoggerFactory.getLogger(CorelatedCriteria_5_3_0_Test.class); + private static final String CDM_DDL_PATH = "/ddl/cdm_v5.3.sql"; + private static final String RESULTS_DDL_PATH = "/ddl/resultsSchema.sql"; + + @BeforeClass + public static void beforeClass() { + jdbcTemplate = new JdbcTemplate(getDataSource()); + prepareSchema("cdm", CDM_DDL_PATH); + } + @Test + public void visitDetailCodesetCriteriaTest() throws Exception { + final CohortExpressionQueryBuilder queryBuilder = new CohortExpressionQueryBuilder(); + final String RESULTS_SCHEMA = "visit_detail_codeset_criteria"; + final String[] testDataSetsPrep = new String[] { "/datasets/vocabulary.json", + "/corelatedcriteria/visitDetailCodesetCriteria_PREP.json"}; + + // Load expected data from an XML dataset + final String[] testDataSetsVerify = new String[] {"/corelatedcriteria/visitDetailCodesetCriteria_VERIFY.json"}; + final IDataSet expectedDataSet = DataSetFactory.createDataSet(testDataSetsVerify); + + // prepare results schema for the specified options.resultSchema + prepareSchema(RESULTS_SCHEMA, RESULTS_DDL_PATH); + + final IDatabaseConnection dbUnitCon = getConnection(); + + // 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" + + // event table query + String eventTable = String.format(CriteriaUtils.EVENT_TABLE_TEMPLATE, RESULTS_SCHEMA + ".cohort", "cdm", 1); + + // Concept set selection criteria + ConceptSetSelection inCsSelection = new ConceptSetSelection(); + inCsSelection.codesetId = 1; + inCsSelection.isExclusion = false; + + ConceptSetSelection notInCsSelection = new ConceptSetSelection(); + notInCsSelection.codesetId = 1; + notInCsSelection.isExclusion = true; + + // VisitDetail criteria + VisitDetail visitDetail = new VisitDetail(); + // build inclusion query for Group Criteria + CriteriaGroup cg = new CriteriaGroup(); + cg.type= "ALL"; + CorelatedCriteria cc = new CorelatedCriteria(); + cc.criteria = visitDetail; // find any condition occurence + cc.startWindow = CriteriaUtils.getPrior365Window(); + cc.occurrence = CriteriaUtils.getAtExactly1Occurrence(); + cg.criteriaList = new CorelatedCriteria[] { cc }; + + // Query 1: exactly 1 occurrence where gener concept in codeset 1 + visitDetail.genderCS = inCsSelection; + + // translate to PG + String inGenderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + inGenderQuery = inGenderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); + String translatedInGenderQuery = SqlRender.renderSql(SqlTranslate.translateSql(inGenderQuery, "postgresql"), + new String[] {"cdm_database_schema", "indexId"}, + new String[] {"cdm", "0"}); + + // Validate results + // perform inclusion query + final ITable actualInGender = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".gender_in_codeset", translatedInGenderQuery); + final ITable expectedInGender = expectedDataSet.getTable(RESULTS_SCHEMA + ".gender_in_codeset"); + Assertion.assertEquals(expectedInGender, actualInGender); + + //Query 2: exaclty 1 occurrence where gender concept not in codeset 1 + visitDetail.genderCS = notInCsSelection; + + // translate to PG + String notInGenderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + notInGenderQuery = notInGenderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); + String translatedNotInGenderQuery = SqlRender.renderSql(SqlTranslate.translateSql(notInGenderQuery, "postgresql"), + new String[] {"cdm_database_schema", "indexId"}, + new String[] {"cdm", "0"}); + + // Validate results + // perform inclusion query + final ITable actualNotInGender = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".gender_not_in_codeset", translatedNotInGenderQuery); + final ITable expectedNotInGender = expectedDataSet.getTable(RESULTS_SCHEMA + ".gender_not_in_codeset"); + Assertion.assertEquals(expectedNotInGender, actualNotInGender); + + // Query 3: exaclty 1 occurrence where provider concept in codeset 1 + visitDetail.genderCS = null; + visitDetail.providerSpecialtyCS = inCsSelection; + + // translate to PG + String inProviderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + inProviderQuery = inProviderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); + String translatedInProviderQuery = SqlRender.renderSql(SqlTranslate.translateSql(inProviderQuery, "postgresql"), + new String[] {"cdm_database_schema", "indexId"}, + new String[] {"cdm", "0"}); + + // Validate results + // perform inclusion query + final ITable actualInProvider = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".provider_in_codeset", translatedInProviderQuery); + final ITable expectedInProvider = expectedDataSet.getTable(RESULTS_SCHEMA + ".provider_in_codeset"); + Assertion.assertEquals(expectedInProvider, actualInProvider); + + // Query 4: exaclty 1 occurrence where provider concept not in codeset 1 + visitDetail.providerSpecialtyCS = notInCsSelection; + + // translate to PG + String notInProviderQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + notInProviderQuery = notInProviderQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); + String translatedNotInProviderQuery = SqlRender.renderSql(SqlTranslate.translateSql(notInProviderQuery, "postgresql"), + new String[] {"cdm_database_schema", "indexId"}, + new String[] {"cdm", "0"}); + + // Validate results + // perform inclusion query + final ITable actualNotInProvider = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".provider_not_in_codeset", translatedNotInProviderQuery); + final ITable expectedNotInProvider = expectedDataSet.getTable(RESULTS_SCHEMA + ".provider_not_in_codeset"); + Assertion.assertEquals(expectedNotInProvider, actualNotInProvider); + + // Query 5: exaclty 1 occurrence where place of service concept in codeset 1 + visitDetail.providerSpecialtyCS = null; + visitDetail.placeOfServiceCS = inCsSelection; + + // translate to PG + String inPlaceOfServiceQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + inPlaceOfServiceQuery = inPlaceOfServiceQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); + String translatedInPlaceOfServiceQuery = SqlRender.renderSql(SqlTranslate.translateSql(inPlaceOfServiceQuery, "postgresql"), + new String[] {"cdm_database_schema", "indexId"}, + new String[] {"cdm", "0"}); + + // Validate results + // perform inclusion query + final ITable actualInPlaceOfService = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".provider_in_codeset", translatedInPlaceOfServiceQuery); + final ITable expectedInPlaceOfService = expectedDataSet.getTable(RESULTS_SCHEMA + ".provider_in_codeset"); + Assertion.assertEquals(expectedInPlaceOfService, actualInPlaceOfService); + + // Query 6: exaclty 1 occurrence where place of service not in codeset 1 + visitDetail.placeOfServiceCS = notInCsSelection; + + // translate to PG + String notInPlaceOfServiceQuery = queryBuilder.getCriteriaGroupQuery(cg, eventTable); + notInPlaceOfServiceQuery = notInPlaceOfServiceQuery.replace("#Codesets", RESULTS_SCHEMA + ".codesets"); + String translatedNotInPlaceOfServiceQuery = SqlRender.renderSql(SqlTranslate.translateSql(notInPlaceOfServiceQuery, "postgresql"), + new String[] {"cdm_database_schema", "indexId"}, + new String[] {"cdm", "0"}); + + // Validate results + // perform inclusion query + final ITable actualNotInPlaceOfService = dbUnitCon.createQueryTable(RESULTS_SCHEMA + ".provider_not_in_codeset", translatedNotInPlaceOfServiceQuery); + final ITable expectedNotInPlaceOfService = expectedDataSet.getTable(RESULTS_SCHEMA + ".provider_not_in_codeset"); + Assertion.assertEquals(expectedNotInPlaceOfService, actualNotInPlaceOfService); + } + +} diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_0_0_Test.java index 96330d81..ed8ffd92 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_0_0_Test.java @@ -145,4 +145,13 @@ public void invalidVisitOccurrence() { options.additionalColumns=Arrays.asList(CriteriaColumn.DAYS_SUPPLY); builder.getCriteriaSql(new VisitOccurrence(), options); } + + @Test + public void invalidVisitDetail() { + exceptionRule.expect(IllegalArgumentException.class); + CriteriaSqlBuilder builder = new VisitDetailSqlBuilder<>(); + BuilderOptions options = new BuilderOptions(); + options.additionalColumns=Arrays.asList(CriteriaColumn.DAYS_SUPPLY); + builder.getCriteriaSql(new VisitDetail(), options); + } } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_3_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_3_0_Test.java new file mode 100644 index 00000000..1b3239b9 --- /dev/null +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaColumn_5_3_0_Test.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 cknoll1. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.circe.cohortdefinition.builders; + +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.ohdsi.circe.cohortdefinition.*; + +/** + * + * @author cknoll1 + */ +public class CriteriaColumn_5_3_0_Test { + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void invalidVisitDetail() { + exceptionRule.expect(IllegalArgumentException.class); + CriteriaSqlBuilder builder = new VisitDetailSqlBuilder<>(); + BuilderOptions options = new BuilderOptions(); + options.additionalColumns=Arrays.asList(CriteriaColumn.DAYS_SUPPLY); + builder.getCriteriaSql(new VisitDetail(), options); + } +} 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 faa8c235..40481a30 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaUtils.java @@ -68,6 +68,14 @@ public static Occurrence getAtExactly0Occurrence() { return exactly0; } + public static Occurrence getAtExactly1Occurrence() { + Occurrence exactly1 = new Occurrence(); + exactly1.type = Occurrence.EXACTLY; + exactly1.count = 1; + + return exactly1; + } + public static Occurrence getDistinctCount(CriteriaColumn countCol, int type, int count) { Occurrence o = new Occurrence(); o.type=type; 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 08461f59..5988a172 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 @@ -319,5 +319,4 @@ public void windowVisitOccurrenceTest() throws Exception { this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); } - } diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_3_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_3_0_Test.java new file mode 100644 index 00000000..a7614636 --- /dev/null +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/WindowCriteria_5_3_0_Test.java @@ -0,0 +1,117 @@ +/* + * Copyright 2020 cknoll1. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.circe.cohortdefinition.builders; + +import com.github.mjeanroy.dbunit.core.dataset.DataSetFactory; +import java.util.Arrays; +import java.util.List; +import org.dbunit.Assertion; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.IDataSet; +import org.dbunit.dataset.ITable; +import org.dbunit.operation.DatabaseOperation; +import org.junit.BeforeClass; +import org.junit.Test; +import org.ohdsi.circe.AbstractDatabaseTest; +import org.ohdsi.circe.cohortdefinition.*; +import org.ohdsi.sql.SqlRender; +import org.ohdsi.sql.SqlTranslate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * + * @author cknoll1 + */ +public class WindowCriteria_5_3_0_Test extends AbstractDatabaseTest { + + private final static Logger LOG = LoggerFactory.getLogger(WindowCriteria_5_3_0_Test.class); + private static final String CDM_DDL_PATH = "/ddl/cdm_v5.3.sql"; + private static final String RESULTS_DDL_PATH = "/ddl/resultsSchema.sql"; + + @BeforeClass + public static void beforeClass() { + jdbcTemplate = new JdbcTemplate(getDataSource()); + prepareSchema("cdm", CDM_DDL_PATH); + } + + private void performWindowTest(WindowedCriteria wc, + String resultsSchema, + String[] prepSets, + String[] verifySets, + List additionalColumns) throws Exception { + final CohortExpressionQueryBuilder queryBuilder = new CohortExpressionQueryBuilder(); + + // prepare results schema for the specified options.resultSchema + prepareSchema(resultsSchema, RESULTS_DDL_PATH); + + final IDatabaseConnection dbUnitCon = getConnection(); + + // load test data into DB. + final IDataSet dsPrep = DataSetFactory.createDataSet(prepSets); + DatabaseOperation.CLEAN_INSERT.execute(dbUnitCon, dsPrep); // clean load of the DB. Careful, clean means "delete the old stuff" + + // translate to PG + String eventTable = String.format(CriteriaUtils.EVENT_TABLE_TEMPLATE, resultsSchema + ".cohort", "cdm", 1); + String query = queryBuilder.getWindowedCriteriaQuery(wc, eventTable); + query = query.replace("#Codesets", resultsSchema + ".codesets"); // any codesets used in criteria shoudl be populated in the results.codesets table + String translatedSql = SqlRender.renderSql(SqlTranslate.translateSql(query, "postgresql"), + new String[]{"cdm_database_schema"}, + new String[]{"cdm"}); + + // Validate results + // Load actual records from cohort table + final ITable actualTable = dbUnitCon.createQueryTable(resultsSchema + ".no_columns", translatedSql); + // Load expected data from an XML dataset + final IDataSet expectedDataSet = DataSetFactory.createDataSet(verifySets); + final ITable expectedTable = expectedDataSet.getTable(resultsSchema + ".no_columns"); + + // Assert actual database table match expected table + Assertion.assertEquals(expectedTable, actualTable); + + // requesting additional columns + BuilderOptions options = new BuilderOptions(); + options.additionalColumns = additionalColumns; + String queryWithColumns = queryBuilder.getWindowedCriteriaQuery(wc, eventTable, options); + queryWithColumns = queryWithColumns.replace("#Codesets", resultsSchema + ".codesets"); // any codesets used in criteria shoudl be populated in the results.codesets table + String translatedWithColumnsSql = SqlRender.renderSql(SqlTranslate.translateSql(queryWithColumns, "postgresql"), + new String[]{"cdm_database_schema"}, + new String[]{"cdm"}); + // Validate results + // Load actual records from cohort table + final ITable actualAdditionalColumnTable = dbUnitCon.createQueryTable(resultsSchema + ".add_columns", translatedWithColumnsSql); + // Load expected data from an XML dataset + final ITable expectedAdditionalColumnTable = expectedDataSet.getTable(resultsSchema + ".add_columns"); + // Assert actual database table match expected table + Assertion.assertEquals(expectedAdditionalColumnTable, actualAdditionalColumnTable); + + } + + @Test + public void windowVisitDetailTest() throws Exception { + + final String resultsSchema = "window_visit_detail"; + final String[] testDataSetsPrep = new String[]{"/datasets/vocabulary.json", + "/windowcriteria/windowVisitDetail_PREP.json"}; + final String[] testDataSetsVerify = new String[]{"/windowcriteria/windowVisitDetail_VERIFY.json"}; + WindowedCriteria wc = new WindowedCriteria(); + wc.criteria = new VisitDetail(); // find any visit detail + wc.startWindow = CriteriaUtils.getPrior365Window(); + List additionalColumns = Arrays.asList(CriteriaColumn.START_DATE, CriteriaColumn.END_DATE, CriteriaColumn.DOMAIN_CONCEPT, CriteriaColumn.DURATION); + this.performWindowTest(wc, resultsSchema, testDataSetsPrep, testDataSetsVerify, additionalColumns); + } +} 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 2a49a225..dd384912 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/printfriendly/PrintFriendlyTest.java @@ -413,6 +413,28 @@ public void visitTest() { )); } +@Test + public void visitDetailTest() { + CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/visitDetail.json")); + String markdown = pf.renderCohort(expression); + assertThat(markdown, stringContainsInOrder( + // concept set name and first in history attribute + "1. visit detail of 'Concept Set 1' (including 'Concept Set 2' source concepts) for the first time in the person's history,", + // age/gender criteria + "who are between 18 and 64 years old, and who have gender concept in 'Concept Set 2' concept set;", + // start date/end date + "starting before January 1, 2010 and ending after January 7, 2010;", + // visit type + "a visit detail type that is in 'Concept Set 2' concept set;", + //provider specialty + "a provider specialty that is in 'Concept Set 3' concept set;", + // visit length + "with length > 12 days", + // nested criteria + "having at least 1 visit detail of 'Concept Set 3', starting anytime on or before 'Concept Set 1' start date." + )); + } + @Test public void dateOffsetTest() { CohortExpression expression = CohortExpression.fromJson(ResourceHelper.GetResourceAsString("/printfriendly/dateOffset.json")); diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json index 90199463..34afceb4 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckCorrect.json @@ -88,6 +88,11 @@ "CodesetId": 0 } }, + { + "VisitDetail": { + "CodesetId": 0 + } + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json index 008fa139..e0d400d5 100644 --- a/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/conceptSetCriteriaCheckIncorrect.json @@ -64,6 +64,9 @@ { "VisitOccurrence": {} }, + { + "VisitDetail": {} + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/domainTypeCheckCorrect.json b/src/test/resources/checkers/domainTypeCheckCorrect.json index 11ef2c00..dc401077 100644 --- a/src/test/resources/checkers/domainTypeCheckCorrect.json +++ b/src/test/resources/checkers/domainTypeCheckCorrect.json @@ -187,6 +187,14 @@ ] } }, + { + "VisitDetail": { + "CodesetId": 0, + "VisitDetailTypeCS": { + "CodesetId": 44787739 + } + } + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/domainTypeCheckIncorrect.json b/src/test/resources/checkers/domainTypeCheckIncorrect.json index 90199463..34afceb4 100644 --- a/src/test/resources/checkers/domainTypeCheckIncorrect.json +++ b/src/test/resources/checkers/domainTypeCheckIncorrect.json @@ -88,6 +88,11 @@ "CodesetId": 0 } }, + { + "VisitDetail": { + "CodesetId": 0 + } + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/drugDomainCheckCorrect.json b/src/test/resources/checkers/drugDomainCheckCorrect.json index f5516121..42e15e84 100644 --- a/src/test/resources/checkers/drugDomainCheckCorrect.json +++ b/src/test/resources/checkers/drugDomainCheckCorrect.json @@ -134,6 +134,11 @@ "CodesetId": 2 } }, + { + "VisitDetail": { + "CodesetId": 2 + } + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/drugDomainCheckIncorrect.json b/src/test/resources/checkers/drugDomainCheckIncorrect.json index 266587a7..4b7364a6 100644 --- a/src/test/resources/checkers/drugDomainCheckIncorrect.json +++ b/src/test/resources/checkers/drugDomainCheckIncorrect.json @@ -134,6 +134,11 @@ "CodesetId": 2 } }, + { + "VisitDetail": { + "CodesetId": 2 + } + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json index 93c914c3..6c79f167 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckCorrect.json @@ -158,6 +158,11 @@ "CodesetId": 0 } }, + { + "VisitDetail": { + "CodesetId": 0 + } + }, { "PayerPlanPeriod": {} } diff --git a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json index 71addca4..e726c78c 100644 --- a/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json +++ b/src/test/resources/checkers/duplicatesCriteriaCheckIncorrect.json @@ -158,6 +158,11 @@ "CodesetId": 0 } }, + { + "VisitDetail": { + "CodesetId": 0 + } + }, { "PayerPlanPeriod": {} } @@ -440,6 +445,26 @@ "Count": 1 } }, + { + "Criteria": { + "VisitDetail": { + "CodesetId": 0 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, { "Criteria": { "PayerPlanPeriod": {} diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json index a4133fb3..145d690a 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueCorrect.json @@ -1087,6 +1087,48 @@ "Count": 1 } }, + { + "Criteria": { + "VisitDetail": { + "OccurrenceStartDate": { + "Value": "2019-12-31", + "Extent": "2020-01-01", + "Op": "bt" + }, + "OccurrenceEndDate": { + "Value": "2019-12-31", + "Extent": "2020-01-01", + "Op": "!bt" + }, + "VisitType": 44787995, + "VisitLength": { + "Value": 1, + "Extent": 2, + "Op": "bt" + }, + "Age": { + "Value": 1, + "Extent": 2, + "Op": "!bt" + }, + "Gender": 4251434, + "ProviderSpecialty": 38003623 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, { "Criteria": { "LocationRegion": { diff --git a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json index b9528345..24864bf0 100644 --- a/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json +++ b/src/test/resources/checkers/primaryCriteriaCheckValueIncorrect.json @@ -558,6 +558,49 @@ "Count": 1 } }, + { + "Criteria": { + "VisitDetail": { + "VisitDetailStartDate": { + "Op": "bt" + }, + "VisitDetailEndDate": { + "Op": "!bt" + }, + "VisitDetailTypeCS": { + "CodesetId": 1000 + }, + "VisitDetailLength": { + "Op": "bt" + }, + "Age": { + "Op": "!bt" + }, + "GenderCS": { + "CodesetId": 1000 + }, + "ProviderSpecialtyCS": { + "CodesetId": 1000 + }, + "PlaceOfServiceCS": { + "CodesetId": 1000 + } + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + }, { "Criteria": { "LocationRegion": {} diff --git a/src/test/resources/corelatedcriteria/distinctVisit_PREP.json b/src/test/resources/corelatedcriteria/distinctVisit_PREP.json index 8d7c0661..adbdb532 100644 --- a/src/test/resources/corelatedcriteria/distinctVisit_PREP.json +++ b/src/test/resources/corelatedcriteria/distinctVisit_PREP.json @@ -22,7 +22,7 @@ "condition_concept_id":2, "condition_start_date":"2000-01-01", "condition_type_concept_id":0, - "visit_occurrence_id": 1 + "visit_occurrence_id": 1 }, { "condition_occurrence_id": 2, @@ -30,7 +30,7 @@ "condition_concept_id":2, "condition_start_date":"2005-01-01", "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 3, @@ -38,7 +38,7 @@ "condition_concept_id":2, "condition_start_date":"2005-01-02", "condition_type_concept_id":0, - "visit_occurrence_id": 2 + "visit_occurrence_id": 2 }, { "condition_occurrence_id": 4, @@ -46,7 +46,7 @@ "condition_concept_id":2, "condition_start_date":"2005-01-01", "condition_type_concept_id":0, - "visit_occurrence_id": 3 + "visit_occurrence_id": 3 }, { "condition_occurrence_id": 5, @@ -54,7 +54,7 @@ "condition_concept_id":2, "condition_start_date":"2005-01-01", "condition_type_concept_id":0, - "visit_occurrence_id": 4 + "visit_occurrence_id": 4 } ], "cdm.observation_period" : [ diff --git a/src/test/resources/corelatedcriteria/distinctVisit_VERIFY.json b/src/test/resources/corelatedcriteria/distinctVisit_VERIFY.json index 925c3b29..77921338 100644 --- a/src/test/resources/corelatedcriteria/distinctVisit_VERIFY.json +++ b/src/test/resources/corelatedcriteria/distinctVisit_VERIFY.json @@ -1,7 +1,7 @@ { "distinct_visit.output": [ { - "index_id":0, + "index_id":0, "person_id":2, "event_id":1 } diff --git a/src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_PREP.json b/src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_PREP.json new file mode 100644 index 00000000..2d486295 --- /dev/null +++ b/src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_PREP.json @@ -0,0 +1,112 @@ +{ + "cdm.person": [ + { + "person_id":1, + "gender_concept_id":1, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + }, + { + "person_id":2, + "gender_concept_id":2, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + } + ], + "cdm.visit_detail": [ + { + "visit_detail_id": 1, + "person_id":1, + "visit_detail_concept_id":3, + "visit_detail_start_date":"2000-01-01", + "visit_detail_end_date":"2000-01-01", + "visit_detail_type_concept_id":0, + "provider_id":1, + "care_site_id":1, + "visit_occurrence_id": 1 + },{ + "visit_detail_id": 2, + "person_id":2, + "visit_detail_concept_id":3, + "visit_detail_start_date":"2000-01-01", + "visit_detail_end_date":"2000-01-01", + "visit_detail_type_concept_id":0, + "provider_id":2, + "care_site_id":2, + "visit_occurrence_id": 1 + } + ], + "cdm.observation_period" : [ + { + "observation_period_id": 1, + "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", + "period_type_concept_id": 0 + } + ], + "cdm.provider": [ + { + "provider_id": 1, + "specialty_concept_id": 1 + },{ + "provider_id": 2, + "specialty_concept_id": 2 + } + ], + "cdm.care_site": [ + { + "care_site_id": 1, + "place_of_service_concept_id": 1 + },{ + "care_site_id": 2, + "place_of_service_concept_id": 2 + } + ], + "visit_detail_codeset_criteria.cohort" : [ + { + "cohort_definition_id": 1, + "subject_id":1, + "cohort_start_date":"2000-06-01", + "cohort_end_date":"2000-06-01" + }, + { + "cohort_definition_id": 1, + "subject_id":2, + "cohort_start_date":"2000-06-01", + "cohort_end_date":"2000-06-01" + } + ], + "visit_detail_codeset_criteria.codesets" : [ + { + "codeset_id": 1, + "concept_id": 1 + },{ + "codeset_id": 1, + "concept_id": 3 + }, + { + "codeset_id": 1, + "concept_id": 5 + },{ + "codeset_id": 2, + "concept_id": 2 + },{ + "codeset_id": 2, + "concept_id": 4 + }, + { + "codeset_id": 2, + "concept_id": 6 + } + ] +} \ No newline at end of file diff --git a/src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_VERIFY.json b/src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_VERIFY.json new file mode 100644 index 00000000..f480e74d --- /dev/null +++ b/src/test/resources/corelatedcriteria/visitDetailCodesetCriteria_VERIFY.json @@ -0,0 +1,29 @@ +{ + "visit_detail_codeset_criteria.gender_in_codeset": [ + { + "index_id":0, + "person_id":1, + "event_id":1 + } + ], + "visit_detail_codeset_criteria.gender_not_in_codeset": [ + { + "index_id":0, + "person_id":2, + "event_id":1 + } + ],"visit_detail_codeset_criteria.provider_in_codeset": [ + { + "index_id":0, + "person_id":1, + "event_id":1 + } + ], + "visit_detail_codeset_criteria.provider_not_in_codeset": [ + { + "index_id":0, + "person_id":2, + "event_id":1 + } + ] +} \ 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 f0742463..eb9ce9af 100644 --- a/src/test/resources/ddl/cdm_v5.0.sql +++ b/src/test/resources/ddl/cdm_v5.0.sql @@ -219,7 +219,6 @@ CREATE TABLE @schemaName.visit_occurrence ) ; - CREATE TABLE @schemaName.procedure_occurrence ( procedure_occurrence_id INTEGER NOT NULL , diff --git a/src/test/resources/ddl/cdm_v5.3.sql b/src/test/resources/ddl/cdm_v5.3.sql new file mode 100644 index 00000000..456ad4b8 --- /dev/null +++ b/src/test/resources/ddl/cdm_v5.3.sql @@ -0,0 +1,542 @@ +CREATE TABLE @schemaName.concept ( + concept_id INTEGER NOT NULL, + concept_name VARCHAR(255) NOT NULL, + domain_id VARCHAR(20) NOT NULL, + vocabulary_id VARCHAR(20) NOT NULL, + concept_class_id VARCHAR(20) NOT NULL, + standard_concept VARCHAR(1) NULL, + concept_code VARCHAR(50) NOT NULL, + valid_start_date DATE NOT NULL, + valid_end_date DATE NOT NULL, + invalid_reason VARCHAR(1) NULL +) +; + +CREATE TABLE @schemaName.vocabulary ( + vocabulary_id VARCHAR(20) NOT NULL, + vocabulary_name VARCHAR(255) NOT NULL, + vocabulary_reference VARCHAR(255) NULL, + vocabulary_version VARCHAR(255) NULL, + vocabulary_concept_id INTEGER NOT NULL +) +; + +CREATE TABLE @schemaName.domain ( + domain_id VARCHAR(20) NOT NULL, + domain_name VARCHAR(255) NOT NULL, + domain_concept_id INTEGER NOT NULL +) +; + +CREATE TABLE @schemaName.concept_class ( + concept_class_id VARCHAR(20) NOT NULL, + concept_class_name VARCHAR(255) NOT NULL, + concept_class_concept_id INTEGER NOT NULL +) +; + +CREATE TABLE @schemaName.concept_relationship ( + concept_id_1 INTEGER NOT NULL, + concept_id_2 INTEGER NOT NULL, + relationship_id VARCHAR(20) NOT NULL, + valid_start_date DATE NOT NULL, + valid_end_date DATE NOT NULL, + invalid_reason VARCHAR(1) NULL) +; + +CREATE TABLE @schemaName.relationship ( + relationship_id VARCHAR(20) NOT NULL, + relationship_name VARCHAR(255) NOT NULL, + is_hierarchical VARCHAR(1) NOT NULL, + defines_ancestry VARCHAR(1) NOT NULL, + reverse_relationship_id VARCHAR(20) NOT NULL, + relationship_concept_id INTEGER NOT NULL +) +; + +CREATE TABLE @schemaName.concept_synonym ( + concept_id INTEGER NOT NULL, + concept_synonym_name VARCHAR(1000) NOT NULL, + language_concept_id INTEGER NOT NULL +) +; + +CREATE TABLE @schemaName.concept_ancestor ( + ancestor_concept_id INTEGER NOT NULL, + descendant_concept_id INTEGER NOT NULL, + min_levels_of_separation INTEGER NOT NULL, + max_levels_of_separation INTEGER NOT NULL +) +; + +CREATE TABLE @schemaName.source_to_concept_map ( + source_code VARCHAR(50) NOT NULL, + source_concept_id INTEGER NOT NULL, + source_vocabulary_id VARCHAR(20) NOT NULL, + source_code_description VARCHAR(255) NULL, + target_concept_id INTEGER NOT NULL, + target_vocabulary_id VARCHAR(20) NOT NULL, + valid_start_date DATE NOT NULL, + valid_end_date DATE NOT NULL, + invalid_reason VARCHAR(1) NULL +) +; + +CREATE TABLE @schemaName.drug_strength ( + drug_concept_id INTEGER NOT NULL, + ingredient_concept_id INTEGER NOT NULL, + amount_value NUMERIC NULL, + amount_unit_concept_id INTEGER NULL, + numerator_value NUMERIC NULL, + numerator_unit_concept_id INTEGER NULL, + denominator_value NUMERIC NULL, + denominator_unit_concept_id INTEGER NULL, + box_size INTEGER NULL, + valid_start_date DATE NOT NULL, + valid_end_date DATE NOT NULL, + invalid_reason VARCHAR(1) NULL +) +; + +CREATE TABLE @schemaName.cohort_definition ( + cohort_definition_id INTEGER NOT NULL, + cohort_definition_name VARCHAR(255) NOT NULL, + cohort_definition_description TEXT NULL, + definition_type_concept_id INTEGER NOT NULL, + cohort_definition_syntax TEXT NULL, + subject_concept_id INTEGER NOT NULL, + cohort_initiation_date DATE NULL +) +; + +CREATE TABLE @schemaName.attribute_definition ( + attribute_definition_id INTEGER NOT NULL, + attribute_name VARCHAR(255) NOT NULL, + attribute_description TEXT NULL, + attribute_type_concept_id INTEGER NOT NULL, + attribute_syntax TEXT NULL +) +; + +CREATE TABLE @schemaName.cdm_source + ( + cdm_source_name VARCHAR(255) NOT NULL, + cdm_source_abbreviation VARCHAR(25) NULL, + cdm_holder VARCHAR(255) NULL, + source_description TEXT NULL, + source_documentation_reference VARCHAR(255) NULL, + cdm_etl_reference VARCHAR(255) NULL, + source_release_date DATE NULL, + cdm_release_date DATE NULL, + cdm_version VARCHAR(10) NULL, + vocabulary_version VARCHAR(20) NULL + ) +; + +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, + birth_datetime TIMESTAMP 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, + 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 , + observation_period_start_datetime TIMESTAMP NULL , + observation_period_end_date DATE NOT NULL , + observation_period_end_datetime TIMESTAMP 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 , + specimen_type_concept_id INTEGER NOT NULL , + specimen_date DATE NOT NULL , + specimen_datetime TIMESTAMP NULL , + quantity NUMERIC NULL , + unit_concept_id INTEGER NULL , + anatomic_site_concept_id INTEGER NULL , + disease_status_concept_id INTEGER NULL , + specimen_source_id VARCHAR(50) NULL , + specimen_source_value VARCHAR(50) NULL , + unit_source_value VARCHAR(50) NULL , + anatomic_site_source_value VARCHAR(50) NULL , + disease_status_source_value VARCHAR(50) NULL + ) +; + +CREATE TABLE @schemaName.death + ( + person_id INTEGER NOT NULL , + death_date DATE NOT NULL , + death_datetime TIMESTAMP 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 , + visit_start_datetime TIMESTAMP NULL , + visit_end_date DATE NOT NULL , + visit_end_datetime TIMESTAMP NULL , + visit_type_concept_id INTEGER NOT NULL , + provider_id INTEGER NULL, + care_site_id INTEGER NULL, + visit_source_value VARCHAR(50) NULL, + visit_source_concept_id INTEGER NULL , + admitting_source_concept_id INTEGER NULL , + admitting_source_value VARCHAR(50) NULL , + discharge_to_concept_id INTEGER NULL , + discharge_to_source_value VARCHAR(50) NULL , + preceding_visit_occurrence_id INTEGER NULL +) +; + +CREATE TABLE @schemaName.visit_detail + ( + visit_detail_id integer NOT NULL, + person_id integer NOT NULL, + visit_detail_concept_id integer NOT NULL, + visit_detail_start_date date NOT NULL, + visit_detail_start_datetime TIMESTAMP NULL, + visit_detail_end_date date NOT NULL, + visit_detail_end_datetime TIMESTAMP NULL, + visit_detail_type_concept_id integer NOT NULL, + provider_id integer NULL, + care_site_id integer NULL, + visit_detail_source_value varchar(50) NULL, + visit_detail_source_concept_id Integer NULL, + admitting_source_value Varchar(50) NULL, + admitting_source_concept_id Integer NULL, + discharge_to_source_value Varchar(50) NULL, + discharge_to_concept_id integer NULL, + preceding_visit_detail_id integer NULL, + visit_detail_parent_id integer NULL, + visit_occurrence_id integer 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_datetime TIMESTAMP 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 , + 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_start_datetime TIMESTAMP NOT NULL , + drug_exposure_end_date DATE NOT NULL , + drug_exposure_end_datetime TIMESTAMP NULL , + verbatim_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 , + lot_number VARCHAR(50) 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_start_datetime TIMESTAMP NOT NULL , + device_exposure_end_date DATE NULL , + device_exposure_end_datetime TIMESTAMP 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 , + 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_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 , + condition_status_source_value VARCHAR(50) NULL , + condition_status_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 , + measurement_datetime TIMESTAMP 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 , + 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 , + note_date DATE NOT NULL , + note_datetime TIMESTAMP NULL , + note_type_concept_id INTEGER NOT NULL , + note_class_concept_id INTEGER NOT NULL , + note_title VARCHAR(250) NULL , + note_text TEXT NOT NULL , + encoding_concept_id INTEGER NOT NULL , + language_concept_id INTEGER NOT NULL , + 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_datetime TIMESTAMP 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 , + observation_source_value VARCHAR(50) 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 , + fact_id_1 INTEGER NOT NULL , + domain_concept_id_2 INTEGER NOT NULL , + fact_id_2 INTEGER NOT NULL , + relationship_concept_id INTEGER NOT 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 , + care_site_name VARCHAR(255) NULL , + place_of_service_concept_id INTEGER NULL , + location_id INTEGER NULL , + care_site_source_value VARCHAR(50) NULL , + place_of_service_source_value VARCHAR(50) NULL + ) +; + +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 , + year_of_birth INTEGER NULL , + gender_concept_id INTEGER NULL , + provider_source_value VARCHAR(50) NULL , + specialty_source_value VARCHAR(50) 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.cost + ( + cost_id INTEGER NOT NULL , + cost_event_id INTEGER NOT NULL , + cost_domain_id VARCHAR(20) NOT NULL , + cost_type_concept_id INTEGER NOT NULL , + currency_concept_id INTEGER NULL , + total_charge NUMERIC NULL , + total_cost NUMERIC NULL , + total_paid NUMERIC NULL , + paid_by_payer NUMERIC NULL , + paid_by_patient NUMERIC NULL , + paid_patient_copay NUMERIC NULL , + paid_patient_coinsurance NUMERIC NULL , + paid_patient_deductible NUMERIC NULL , + paid_by_primary NUMERIC NULL , + paid_ingredient_cost NUMERIC NULL , + paid_dispensing_fee NUMERIC NULL , + payer_plan_period_id INTEGER NULL , + amount_allowed NUMERIC NULL , + revenue_code_concept_id INTEGER NULL , + reveue_code_source_value VARCHAR(50) NULL , + drg_concept_id INTEGER NULL, + drg_source_value VARCHAR(3) NULL + ) +; + +CREATE TABLE @schemaName.cohort + ( + cohort_definition_id INTEGER NOT NULL , + subject_id INTEGER 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 , + 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 , + drug_exposure_count INTEGER NULL , + gap_days INTEGER NULL + ) +; + +CREATE TABLE @schemaName.dose_era + ( + 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 + ) +; + +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 + ) +; \ No newline at end of file diff --git a/src/test/resources/ddl/resultsSchema.sql b/src/test/resources/ddl/resultsSchema.sql index 9ca977c6..8971aada 100644 --- a/src/test/resources/ddl/resultsSchema.sql +++ b/src/test/resources/ddl/resultsSchema.sql @@ -39,3 +39,8 @@ CREATE TABLE @schemaName.cohort_censor_stats ( cohort_definition_id int NOT NULL, lost_count BIGINT NOT NULL ); + +CREATE TABLE @schemaName.codesets ( + codeset_id int NOT NULL, + concept_id int NOT NULL +); diff --git a/src/test/resources/printfriendly/nullCodesetId.json b/src/test/resources/printfriendly/nullCodesetId.json index 6cef23cb..b2e94068 100644 --- a/src/test/resources/printfriendly/nullCodesetId.json +++ b/src/test/resources/printfriendly/nullCodesetId.json @@ -327,6 +327,35 @@ "Groups": [] } } + }, + { + "VisitDetail": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "VisitDetail": {} + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Coeff": 1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + } + } } ], "ObservationWindow": { diff --git a/src/test/resources/printfriendly/visitDetail.json b/src/test/resources/printfriendly/visitDetail.json new file mode 100644 index 00000000..a3dd827b --- /dev/null +++ b/src/test/resources/printfriendly/visitDetail.json @@ -0,0 +1,115 @@ +{ + "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": [ + { + "VisitDetail": { + "CorrelatedCriteria": { + "Type": "ALL", + "CriteriaList": [ + { + "Criteria": { + "VisitDetail": { + "CodesetId": 2 + } + }, + "StartWindow": { + "Start": { + "Coeff": -1 + }, + "End": { + "Days": 0, + "Coeff": -1 + }, + "UseEventEnd": false + }, + "Occurrence": { + "Type": 2, + "Count": 1 + } + } + ], + "DemographicCriteriaList": [], + "Groups": [] + }, + "CodesetId": 0, + "VisitDetailStartDate": { + "Value": "2010-01-01", + "Op": "lt" + }, + "VisitDetailEndDate": { + "Value": "2010-01-07", + "Op": "gt" + }, + "VisitDetailTypeCS": { + "CodesetId": 1 + }, + "VisitDetailSourceConcept": 1, + "VisitDetailLength": { + "Value": 12, + "Op": "gt" + }, + "First": true, + "Age": { + "Value": 18, + "Extent": 64, + "Op": "bt" + }, + "GenderCS": { + "CodesetId": 1 + }, + "ProviderSpecialtyCS": { + "CodesetId": 2 + }, + "PlaceOfService": { + "CodesetId": 0 + }, + "PlaceOfServiceLocation": 2 + } + } + ], + "ObservationWindow": { + "PriorDays": 0, + "PostDays": 0 + }, + "PrimaryCriteriaLimit": { + "Type": "First" + } + }, + "QualifiedLimit": { + "Type": "First" + }, + "ExpressionLimit": { + "Type": "First" + }, + "InclusionRules": [], + "CensoringCriteria": [], + "CollapseSettings": { + "CollapseType": "ERA", + "EraPad": 0 + }, + "CensorWindow": {}, + "cdmVersionRange": ">=5.3.0" +} \ No newline at end of file diff --git a/src/test/resources/windowcriteria/windowVisitDetail_PREP.json b/src/test/resources/windowcriteria/windowVisitDetail_PREP.json new file mode 100644 index 00000000..97a50eaa --- /dev/null +++ b/src/test/resources/windowcriteria/windowVisitDetail_PREP.json @@ -0,0 +1,117 @@ +{ + "cdm.person": [ + { + "person_id":1, + "gender_concept_id":0, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + } + ], + "cdm.visit_occurrence": [ + { + "visit_occurrence_id": 1, + "person_id":1, + "visit_concept_id":2, + "visit_type_concept_id":0, + "visit_start_date":"2000-01-01", + "visit_end_date":"2000-01-07" + }, + { + "visit_occurrence_id": 2, + "person_id":1, + "visit_concept_id":2, + "visit_type_concept_id":0, + "visit_start_date":"2005-01-01", + "visit_end_date":"2005-01-07" + }, + { + "visit_occurrence_id": 3, + "person_id":1, + "visit_concept_id":2, + "visit_type_concept_id":0, + "visit_start_date":"2005-02-01", + "visit_end_date":"2005-02-07" + }, + { + "visit_occurrence_id": 4, + "person_id":1, + "visit_concept_id":2, + "visit_type_concept_id":0, + "visit_start_date":"2005-03-01", + "visit_end_date":"2005-03-07" + }, + { + "visit_occurrence_id": 5, + "person_id":1, + "visit_concept_id":2, + "visit_type_concept_id":0, + "visit_start_date":"2005-04-01", + "visit_end_date":"2005-04-07" + } + ], + "cdm.visit_detail": [ + { + "visit_detail_id": 1, + "person_id":1, + "visit_detail_concept_id":2, + "visit_detail_type_concept_id":0, + "visit_detail_start_date":"2000-01-01", + "visit_detail_end_date":"2000-01-07", + "visit_occurrence_id": 1 + }, + { + "visit_detail_id": 2, + "person_id":1, + "visit_detail_concept_id":2, + "visit_detail_type_concept_id":0, + "visit_detail_start_date":"2005-01-01", + "visit_detail_end_date":"2005-01-07", + "visit_occurrence_id": 2 + }, + { + "visit_detail_id": 3, + "person_id":1, + "visit_detail_concept_id":2, + "visit_detail_type_concept_id":0, + "visit_detail_start_date":"2005-02-01", + "visit_detail_end_date":"2005-02-07", + "visit_occurrence_id": 3 + }, + { + "visit_detail_id": 4, + "person_id":1, + "visit_detail_concept_id":2, + "visit_detail_type_concept_id":0, + "visit_detail_start_date":"2005-03-01", + "visit_detail_end_date":"2005-03-07", + "visit_occurrence_id": 4 + }, + { + "visit_detail_id": 5, + "person_id":1, + "visit_detail_concept_id":2, + "visit_detail_type_concept_id":0, + "visit_detail_start_date":"2005-04-01", + "visit_detail_end_date":"2005-04-07", + "visit_occurrence_id": 5 + } + ], + "cdm.observation_period" : [ + { + "observation_period_id": 1, + "person_id":1, + "observation_period_start_date":"2000-01-01", + "observation_period_end_date":"2010-01-01", + "period_type_concept_id": 0 + } + ], + "window_visit_detail.cohort" : [ + { + "cohort_definition_id": 1, + "subject_id":1, + "cohort_start_date":"2006-01-01", + "cohort_end_date":"2006-01-01" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/windowcriteria/windowVisitDetail_VERIFY.json b/src/test/resources/windowcriteria/windowVisitDetail_VERIFY.json new file mode 100644 index 00000000..24997582 --- /dev/null +++ b/src/test/resources/windowcriteria/windowVisitDetail_VERIFY.json @@ -0,0 +1,54 @@ +{ + "window_visit_detail.no_columns": [ + { + "person_id":1, + "event_id":1 + }, + { + "person_id":1, + "event_id":1 + }, + { + "person_id":1, + "event_id":1 + }, + { + "person_id":1, + "event_id":1 + } + ], + "window_visit_detail.add_columns": [ + { + "person_id":1, + "event_id":1, + "start_date": "2005-01-01", + "end_date": "2005-01-07", + "domain_concept_id": 2, + "duration": 6 + }, + { + "person_id":1, + "event_id":1, + "start_date": "2005-02-01", + "end_date": "2005-02-07", + "domain_concept_id": 2, + "duration": 6 + }, + { + "person_id":1, + "event_id":1, + "start_date": "2005-03-01", + "end_date": "2005-03-07", + "domain_concept_id": 2, + "duration": 6 + }, + { + "person_id":1, + "event_id":1, + "start_date": "2005-04-01", + "end_date": "2005-04-07", + "domain_concept_id": 2, + "duration": 6 + } + ] +} \ No newline at end of file