From 60f847b5a528c37020c77f9ce5d75bf9c168a9d6 Mon Sep 17 00:00:00 2001 From: ymolodkov Date: Fri, 20 Feb 2026 17:21:57 +0100 Subject: [PATCH] Fix value_as_concept_id missing from CTE when Measurement abnormal=true --- .../builders/MeasurementSqlBuilder.java | 13 ++- .../builders/CriteriaQuery_5_0_0_Test.java | 53 +++++++++--- .../criteria/measurementAbnormal_PREP.json | 86 +++++++++++++++++++ .../criteria/measurementAbnormal_VERIFY.json | 28 ++++++ 4 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 src/test/resources/criteria/measurementAbnormal_PREP.json create mode 100644 src/test/resources/criteria/measurementAbnormal_VERIFY.json diff --git a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java index e5076f29..733c798e 100644 --- a/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java +++ b/src/main/java/org/ohdsi/circe/cohortdefinition/builders/MeasurementSqlBuilder.java @@ -1,5 +1,6 @@ package org.ohdsi.circe.cohortdefinition.builders; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.ohdsi.circe.cohortdefinition.Measurement; import org.ohdsi.circe.helper.ResourceHelper; @@ -102,10 +103,8 @@ protected List resolveSelectClauses(T criteria) { selectCols.add("m.operator_concept_id"); } - // valueAsConcept - if ((criteria.valueAsConcept != null && criteria.valueAsConcept.length > 0) || - criteria.valueAsConceptCS != null - ) { + // valueAsConcept or abnormal + if (ArrayUtils.isNotEmpty(criteria.valueAsConcept) || criteria.valueAsConceptCS != null || Boolean.TRUE.equals(criteria.abnormal)) { selectCols.add("m.value_as_concept_id"); } @@ -140,7 +139,7 @@ protected List resolveJoinClauses(T criteria) { List joinClauses = new ArrayList<>(); // join to PERSON - if (criteria.age != null || + if (criteria.age != null || (criteria.gender != null && criteria.gender.length > 0) || criteria.genderCS != null ) { @@ -207,7 +206,7 @@ protected List resolveWhereClauses(T criteria) { if (criteria.valueAsConceptCS != null) { whereClauses.add(getCodesetInExpression("C.value_as_concept_id", criteria.valueAsConceptCS)); } - + // unit if (criteria.unit != null && criteria.unit.length > 0) { ArrayList conceptIds = getConceptIdsFromConcepts(criteria.unit); @@ -218,7 +217,7 @@ protected List resolveWhereClauses(T criteria) { if (criteria.unitCS != null) { whereClauses.add(getCodesetInExpression("C.unit_concept_id", criteria.unitCS)); } - + // rangeLow if (criteria.rangeLow != null) { whereClauses.add(buildNumericRangeClause("C.range_low", criteria.rangeLow, ".4f")); diff --git a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaQuery_5_0_0_Test.java b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaQuery_5_0_0_Test.java index 6df7cc88..6e4f7deb 100644 --- a/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaQuery_5_0_0_Test.java +++ b/src/test/java/org/ohdsi/circe/cohortdefinition/builders/CriteriaQuery_5_0_0_Test.java @@ -27,6 +27,8 @@ import org.dbunit.operation.DatabaseOperation; import org.junit.BeforeClass; import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import org.ohdsi.circe.AbstractDatabaseTest; import org.ohdsi.circe.cohortdefinition.ConceptSetSelection; import org.ohdsi.circe.cohortdefinition.ConditionEra; @@ -64,7 +66,7 @@ public class CriteriaQuery_5_0_0_Test extends AbstractDatabaseTest { private static final ConceptSetSelection CONCEPTSET_2 = null; private static final ConceptSetSelection CONCEPTSET_3 = null; private static final ConceptSetSelection CONCEPTSET_4 = null; - + private String renderQuery(String query) { String result = StringUtils.replace(query, "#Codesets", "temp.codesets"); @@ -91,7 +93,7 @@ public static void beforeClass() { prepareSchema("cdm", CDM_DDL_PATH); prepareSchema("temp", TEMP_DDL_PATH); } - + private ConceptSetSelection createConceptSetSelection(Integer id, boolean isExcluded) { ConceptSetSelection css = new ConceptSetSelection(); css.codesetId = id; @@ -147,7 +149,7 @@ public void testConditionEraDateOffset() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedDataSet, actualDataSet); } - + @Test public void testConditionEraConceptSet() throws Exception { final String[] testDataSetsPrep = new String[]{ @@ -252,7 +254,7 @@ public void testConditionOccurrenceConceptSet() throws Exception { criteria.genderCS = createConceptSetSelection(1,false); criteria.visitTypeCS = createConceptSetSelection(2,false); criteria.providerSpecialtyCS = createConceptSetSelection(3,false); - + ConditionOccurrenceSqlBuilder builder = new ConditionOccurrenceSqlBuilder<>(); String query = renderQuery(builder.getCriteriaSql(criteria)); @@ -270,7 +272,7 @@ public void testConditionOccurrenceConceptSet() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedDataSet, actualDataSet); } - + @Test public void testDeathDateOffset() throws Exception { final String[] testDataSetsPrep = new String[]{ @@ -315,7 +317,7 @@ public void testDeathDateOffset() throws Exception { Assertion.assertEquals(expectedDataSet, actualDataSet); } - + @Test public void testDeathConceptSet() throws Exception { final String[] testDataSetsPrep = new String[]{ @@ -505,7 +507,7 @@ public void testDoseEraConceptSet() throws Exception { criteria.codesetId = 1; criteria.genderCS = createConceptSetSelection(1,false); criteria.unitCS = createConceptSetSelection(3,false); - + DoseEraSqlBuilder builder = new DoseEraSqlBuilder<>(); String query = renderQuery(builder.getCriteriaSql(criteria)); @@ -522,7 +524,7 @@ public void testDoseEraConceptSet() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedDataSet, actualDataSet); - } + } @Test public void testDrugEraDateOffset() throws Exception { @@ -691,7 +693,7 @@ public void testDrugExposureConceptSet() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedDataSet, actualDataSet); - } + } @Test public void testMeasurementDateOffset() throws Exception { @@ -763,7 +765,7 @@ public void testMeasurementConceptSet() throws Exception { criteria.genderCS = createConceptSetSelection(1,false); criteria.providerSpecialtyCS = createConceptSetSelection(3,false); criteria.visitTypeCS = createConceptSetSelection(2,false); - + MeasurementSqlBuilder builder = new MeasurementSqlBuilder<>(); String query = renderQuery(builder.getCriteriaSql(criteria)); @@ -942,7 +944,7 @@ public void testObservationPeriodConceptSet() throws Exception { // Test 1: simple query with no special conditions ObservationPeriod criteria = new ObservationPeriod(); criteria.periodTypeCS = createConceptSetSelection(1,false); - + ObservationPeriodSqlBuilder builder = new ObservationPeriodSqlBuilder<>(); String query = renderQuery(builder.getCriteriaSql(criteria)); @@ -1082,7 +1084,7 @@ public void testProcedureOccurrenceConceptSet() throws Exception { criteria.procedureTypeCS = createConceptSetSelection(2,false); criteria.visitTypeCS = createConceptSetSelection(2,false); criteria.providerSpecialtyCS = createConceptSetSelection(3,false); - + ProcedureOccurrenceSqlBuilder builder = new ProcedureOccurrenceSqlBuilder<>(); String query = renderQuery(builder.getCriteriaSql(criteria)); @@ -1124,7 +1126,7 @@ public void testSpecimenConceptSet() throws Exception { criteria.unitCS = createConceptSetSelection(3,false); criteria.anatomicSiteCS = createConceptSetSelection(2,false); criteria.diseaseStatusCS = createConceptSetSelection(3,false); - + SpecimenSqlBuilder builder = new SpecimenSqlBuilder<>(); String query = renderQuery(builder.getCriteriaSql(criteria)); @@ -1226,4 +1228,29 @@ public void testVisitOccurrenceConceptSet() throws Exception { // Assert actual database table match expected table Assertion.assertEquals(expectedDataSet, actualDataSet); } + + @Test + public void testMeasurementAbnormal() throws Exception { + final IDatabaseConnection dbUnitCon = getConnection(); + DatabaseOperation.CLEAN_INSERT.execute(dbUnitCon, DataSetFactory.createDataSet(new String[]{ "/datasets/vocabulary.json", "/criteria/codesets.json", "/criteria/measurementAbnormal_PREP.json"})); + + Measurement criteria = new Measurement(); + criteria.codesetId = 1; + criteria.abnormal = true; + + String query = renderQuery(new MeasurementSqlBuilder<>().getCriteriaSql(criteria)); + + assertThat(query, containsString("m.value_as_concept_id")); + assertThat(query, containsString("C.value_as_number < C.range_low")); + assertThat(query, containsString("C.value_as_number > C.range_high")); + assertThat(query, containsString("C.value_as_concept_id in (4155142, 4155143)")); + + // Verify the query executes without error and returns only abnormal measurements + ITable actual = new SortedTable( + dbUnitCon.createQueryTable("measurement.abnormal", query), + new String[]{"person_id", "start_date"} + ); + IDataSet expected = DataSetFactory.createDataSet(new String[]{"/criteria/measurementAbnormal_VERIFY.json"}); + Assertion.assertEquals(expected, new CompositeDataSet(new ITable[]{actual})); + } } diff --git a/src/test/resources/criteria/measurementAbnormal_PREP.json b/src/test/resources/criteria/measurementAbnormal_PREP.json new file mode 100644 index 00000000..e4854dba --- /dev/null +++ b/src/test/resources/criteria/measurementAbnormal_PREP.json @@ -0,0 +1,86 @@ +{ + "cdm.person": [ + { + "person_id":1, + "gender_concept_id":0, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + }, + { + "person_id":2, + "gender_concept_id":0, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + }, + { + "person_id":3, + "gender_concept_id":0, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + }, + { + "person_id":4, + "gender_concept_id":0, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + }, + { + "person_id":5, + "gender_concept_id":0, + "year_of_birth":0, + "race_concept_id":0, + "ethnicity_concept_id":0 + } + ], + "cdm.measurement": [ + { + "measurement_id": 1, + "person_id":1, + "measurement_concept_id":1, + "measurement_date":"2000-01-01", + "measurement_type_concept_id":0, + "value_as_number":5, + "range_low":10, + "range_high":20 + }, + { + "measurement_id": 2, + "person_id":2, + "measurement_concept_id":1, + "measurement_date":"2000-01-01", + "measurement_type_concept_id":0, + "value_as_concept_id":4155142 + }, + { + "measurement_id": 3, + "person_id":3, + "measurement_concept_id":1, + "measurement_date":"2000-01-01", + "measurement_type_concept_id":0, + "value_as_number":15, + "range_low":10, + "range_high":20 + }, + { + "measurement_id": 4, + "person_id":4, + "measurement_concept_id":1, + "measurement_date":"2000-01-01", + "measurement_type_concept_id":0 + }, + { + "measurement_id": 5, + "person_id":5, + "measurement_concept_id":1, + "measurement_date":"2000-01-01", + "measurement_type_concept_id":0, + "value_as_number":25, + "range_low":10, + "range_high":20 + } + ] +} diff --git a/src/test/resources/criteria/measurementAbnormal_VERIFY.json b/src/test/resources/criteria/measurementAbnormal_VERIFY.json new file mode 100644 index 00000000..8649a02e --- /dev/null +++ b/src/test/resources/criteria/measurementAbnormal_VERIFY.json @@ -0,0 +1,28 @@ +{ + "measurement.abnormal": [ + { + "person_id": 1, + "event_id": 1, + "start_date": "2000-01-01", + "end_date": "2000-01-02", + "visit_occurrence_id": null, + "sort_date": "2000-01-01" + }, + { + "person_id": 2, + "event_id": 2, + "start_date": "2000-01-01", + "end_date": "2000-01-02", + "visit_occurrence_id": null, + "sort_date": "2000-01-01" + }, + { + "person_id": 5, + "event_id": 5, + "start_date": "2000-01-01", + "end_date": "2000-01-02", + "visit_occurrence_id": null, + "sort_date": "2000-01-01" + } + ] +}