Skip to content

Commit

Permalink
🦍 Big refactor to get the HF phenotype running.
Browse files Browse the repository at this point in the history
  • Loading branch information
psbrandt committed Dec 23, 2019
1 parent f484599 commit b52d863
Show file tree
Hide file tree
Showing 44 changed files with 1,409 additions and 971 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@
<dependency>
<groupId>edu.phema</groupId>
<artifactId>elm-utils</artifactId>
<version>0.0.7</version>
<version>0.0.8</version>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public JsonObject cqlToOmopJsonObject(String cqlString, String statementName) th
* @throws Exception
*/
private CohortDefinitionDTO buildCohortDefinition(String name, String description, CohortExpression cohortExpression) throws Exception {
CohortDefinitionDTO cohortDefinition = CirceUtil.getDefaultCohortDefinition();
CohortDefinitionDTO cohortDefinition = CirceUtil.defaultCohortDefinition();

ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Expand Down
88 changes: 53 additions & 35 deletions src/main/java/edu/phema/elm_to_omop/helper/CirceUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,6 @@
* generate invalid objects using the empty default constructors.
*/
public class CirceUtil {

/**
* The translator focuses on creating InclusionRules (CriteraGroups really),
* and we let the initial population consist of anyone with any visit
* occurrence
*
* @return A PrimaryCriteria representing any visit occurrence
*/
public static PrimaryCriteria getDefaultPrimaryCriteria() {
PrimaryCriteria primaryCriteria = new PrimaryCriteria();

primaryCriteria.observationWindow = new ObservationFilter();
primaryCriteria.criteriaList = new Criteria[]{
new VisitOccurrence()
};

return primaryCriteria;
}

/**
* Extends a primitive array of CriteriaGroups by adding a new CriteriaGroup.
* This is necessary because Circe uses primitive arrays instead of Java collections.
Expand Down Expand Up @@ -78,22 +59,21 @@ public static DemographicCriteria[] addDemographicCriteria(DemographicCriteria[]
}

/**
* InclusionRules are just a thin wrapper around CriteriaGroups, which contain the
* expression logic. This method performs the wrapping.
* The translator focuses on creating InclusionRules (CriteraGroups really),
* and we let the initial population consist of anyone with any visit
* occurrence
*
* @param name The name of the InclusionRule
* @param description The description of the InclusionRule
* @param criteriaGroup The CriteriaGroup (expression) to wrap
* @return The created InclusionRule
* @return A PrimaryCriteria representing any visit occurrence
*/
public static InclusionRule inclusionRuleFromCriteriaGroup(String name, String description, CriteriaGroup criteriaGroup) {
InclusionRule inclusionRule = new InclusionRule();
public static PrimaryCriteria defaultPrimaryCriteria() {
PrimaryCriteria primaryCriteria = new PrimaryCriteria();

inclusionRule.name = name;
inclusionRule.description = description;
inclusionRule.expression = criteriaGroup;
primaryCriteria.observationWindow = new ObservationFilter();
primaryCriteria.criteriaList = new Criteria[]{
new VisitOccurrence()
};

return inclusionRule;
return primaryCriteria;
}

/**
Expand Down Expand Up @@ -138,14 +118,52 @@ public static Occurrence defaultOccurrence() {
*
* @return The default cohort definition
*/
public static CohortDefinitionService.CohortDefinitionDTO getDefaultCohortDefinition() {
public static CohortDefinitionService.CohortDefinitionDTO defaultCohortDefinition() {
CohortDefinitionService.CohortDefinitionDTO cohortDefinition = new CohortDefinitionService.CohortDefinitionDTO();

cohortDefinition.expressionType = ExpressionType.SIMPLE_EXPRESSION;

return cohortDefinition;
}

/**
* InclusionRules are just a thin wrapper around CriteriaGroups, which contain the
* expression logic. This method performs the wrapping.
*
* @param name The name of the InclusionRule
* @param description The description of the InclusionRule
* @param criteriaGroup The CriteriaGroup (expression) to wrap
* @return The created InclusionRule
*/
public static InclusionRule inclusionRuleFromCriteriaGroup(String name, String description, CriteriaGroup criteriaGroup) {
InclusionRule inclusionRule = new InclusionRule();

inclusionRule.name = name;
inclusionRule.description = description;
inclusionRule.expression = criteriaGroup;

return inclusionRule;
}

/**
* Returns a CorelatedCriteria from a Circe Criteria instance, Occurrence object, and restrictVisit boolean
*
* @param criteria The Criteria instance to wrap
* @param occurrence The Occurrence object
* @param restrictVisit Whether or not to restrict the CorelatedCriteria to same visit as the parent Criteria or
* cohort entry event
* @return The created CorelatedCriteria
*/
public static CorelatedCriteria corelatedCriteriaFromCriteria(Criteria criteria, Occurrence occurrence, boolean restrictVisit) {
CorelatedCriteria corelatedCriteria = defaultCorelatedCriteria();

corelatedCriteria.criteria = criteria;
corelatedCriteria.occurrence = occurrence;
corelatedCriteria.restrictVisit = restrictVisit;

return corelatedCriteria;
}

/**
* Generates a Circe CriteriaGroup from a single CorelatedCriteria and group inclusion properties
*
Expand All @@ -154,7 +172,7 @@ public static CohortDefinitionService.CohortDefinitionDTO getDefaultCohortDefini
* @param count The inclusion count
* @return The created CriteriaGroup
*/
public static CriteriaGroup groupFromCorelatedCriteria(CorelatedCriteria criteria, CirceConstants.CriteriaGroupType type, Integer count) {
public static CriteriaGroup criteriaGroupFromCorelatedCriteria(CorelatedCriteria criteria, CirceConstants.CriteriaGroupType type, Integer count) {
CriteriaGroup group = new CriteriaGroup();

group.criteriaList = new CorelatedCriteria[]{criteria};
Expand All @@ -174,12 +192,12 @@ public static CriteriaGroup groupFromCorelatedCriteria(CorelatedCriteria criteri
* @param count The inclusion count
* @return The created CriteriaGroup
*/
public static CriteriaGroup groupFromCriteria(Criteria criteria, CirceConstants.CriteriaGroupType type, Integer count, boolean restrictVisit) {
public static CriteriaGroup criteriaGroupFromCriteria(Criteria criteria, CirceConstants.CriteriaGroupType type, Integer count, boolean restrictVisit) {
CorelatedCriteria corelatedCriteria = defaultCorelatedCriteria();

corelatedCriteria.criteria = criteria;
corelatedCriteria.restrictVisit = restrictVisit;

return groupFromCorelatedCriteria(corelatedCriteria, type, count);
return criteriaGroupFromCorelatedCriteria(corelatedCriteria, type, count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package edu.phema.elm_to_omop.translate;

import edu.phema.elm_to_omop.helper.CirceUtil;
import edu.phema.elm_to_omop.translate.criteria.CriteriaTranslator;
import edu.phema.elm_to_omop.translate.criteria.comparison.ComparisonExpressionTranslator;
import edu.phema.elm_to_omop.translate.criteria.correlation.CorrelatedQueryTranslator;
import edu.phema.elm_to_omop.translate.exception.PhemaNotImplementedException;
import org.hl7.elm.r1.*;
import org.ohdsi.circe.cohortdefinition.CorelatedCriteria;
import org.ohdsi.circe.cohortdefinition.Occurrence;

import java.util.List;

/**
* Class responsible for generating CorelatedCriteria [sic]. CorelatedCriteria criteria are used for applying
* restrictions to Circe domain Criteria (e.g. ConditionOccurrence).
* <p>
* These restrictions include:
* <p>
* - start and end window restrictions relative to the index event (the containing Criteria, or the cohort entry event)
* - occurrence count (e.g. EXACTLY 3 procedures, AT_LEAST 1 condition)
* - visit restriction relative to the index event (the containing Criteria, or the cohort entry event)
* <p>
* Confusingly, a Criteria has field called CorelatedCriteria, which is of type CriteriaGroup.
*/
public class CorelatedCriteriaTranslator {
private static CorelatedCriteria generateCorelatedCriteriaForRetrieve(Retrieve retrieve, PhemaElmaToOmopTranslatorContext context) throws Exception {
Occurrence occurrence = CirceUtil.defaultOccurrence();

CorelatedCriteria corelatedCriteria = CirceUtil.defaultCorelatedCriteria();
corelatedCriteria.occurrence = occurrence;

corelatedCriteria.criteria = CriteriaTranslator.generateCriteriaForExpression(retrieve, context);

return corelatedCriteria;
}

private static CorelatedCriteria generateCorelatedCriteriaForQuery(Query query, PhemaElmaToOmopTranslatorContext context) throws Exception {
List<RelationshipClause> relationships = query.getRelationship();

if (relationships == null || relationships.size() == 0) {
// We are dealing with an uncorrelated query

// We don't currently support where clause filtering
if (query.getWhere() != null) {
throw new PhemaNotImplementedException("Query filtering which Where clause is currently unsupported");
}

// Right now we basic just support a simple aliased query like [Condition: "valueset"] C, which is exactly
// the same a simple retrieve, so we evaluate it as such. If there are no relationships, there should only
// be one AliasedSource
return generateCorelatedCriteriaForExpression(query.getSource().get(0).getExpression(), context);
} else if (relationships.size() == 1) {
// We are dealing with a correlated query
return CorrelatedQueryTranslator.generateCorelatedCriteriaForCorrelatedQuery(query, context);
} else {
throw new PhemaNotImplementedException("The translator is currently only able to handle a single relationship for a data element");
}
}

/**
* Given an Expression from CQL/ELM, convert it into an OHDSI CorelatedCriteria.
*
* @param expression The ELM expression
* @param context The ELM translation context
* @return The CorelatedCriteria for the expression
* @throws Exception
*/
public static CorelatedCriteria generateCorelatedCriteriaForExpression(Expression expression, PhemaElmaToOmopTranslatorContext context) throws Exception {
if (expression instanceof Retrieve) {
return generateCorelatedCriteriaForRetrieve((Retrieve) expression, context);
} else if (expression instanceof Exists) {
return generateCorelatedCriteriaForExpression(((Exists) expression).getOperand(), context);
} else if (expression instanceof Query) {
return generateCorelatedCriteriaForQuery((Query) expression, context);
} else if (ComparisonExpressionTranslator.isNumericComparison(expression)) {
return ComparisonExpressionTranslator.generateCorelatedCriteriaForComparison(expression, context);
} else {
// TODO - Need to handle more than simple query types
throw new PhemaNotImplementedException(String.format("Unable to generate CorelatedCriteria for type: %s", expression.getClass().getName()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package edu.phema.elm_to_omop.translate;

import edu.phema.elm_to_omop.helper.CirceUtil;
import edu.phema.elm_to_omop.translate.criteria.demographic.DemographicExpressionTranslator;
import edu.phema.elm_to_omop.translate.exception.PhemaNotImplementedException;
import org.hl7.elm.r1.And;
import org.hl7.elm.r1.BinaryExpression;
import org.hl7.elm.r1.Expression;
import org.hl7.elm.r1.Or;
import org.ohdsi.circe.cohortdefinition.CorelatedCriteria;
import org.ohdsi.circe.cohortdefinition.CriteriaGroup;
import org.ohdsi.circe.cohortdefinition.DemographicCriteria;

/**
* Class responsible for building CriteriaGroups. CriteriaGroups are used to group a potentially heterogeneous
* collection of CorelatedCriteria, DemographicCriteria, and/or other CriteriaGroups together when you want all or some
* of them to apply.
* <p>
* CriteriaGroups can represent the following types of relationships:
* <p>
* - ALL
* - ANY
* - AT_LEAST x
* - AT_MOST x
* <p>
* where x is some integer.
* <p>
* In CQL, these ideas can be represented using AND and OR statements, or by using the Count aggregate expression.
*/
public class CriteriaGroupTranslator {
/**
* Generates a Circe CriteriaGroup from a ELM BinaryExpression. Creating "ANY" (OR) and "ALL" (AND) CriteriaGroups
* is how nested boolean logic is implemented. Only these two boolean expressions generated CriteriaGroups. Every
* other type of ELM expression generates a CorelatedCriteria
*
* @param expression The ELM BinaryExpression (must be And or Or)
* @param context The translation context
* @return The created "ANY" or "ALL" CriteriaGroup
* @throws Exception
*/
private static CriteriaGroup generateCriteriaGroupForBinaryExpression(BinaryExpression expression, PhemaElmaToOmopTranslatorContext context) throws Exception {
CriteriaGroup criteriaGroup = new CriteriaGroup();
criteriaGroup.type = PhemaElmToOmopTranslator.getInclusionExpressionType(expression).toString();

for (Expression operand : expression.getOperand()) {
Expression operandExp = operand;

if (DemographicExpressionTranslator.isDemographicExpression(operandExp)) {
DemographicCriteria demographicCriteria = DemographicExpressionTranslator.generateDemographicCriteriaForExpression(operandExp);

criteriaGroup.demographicCriteriaList = CirceUtil.addDemographicCriteria(criteriaGroup.demographicCriteriaList, demographicCriteria);
} else {
// Are we nesting even further?
if (PhemaElmToOmopTranslator.isBooleanExpression(operandExp)) {
criteriaGroup.groups = CirceUtil.addCriteriaGroup(criteriaGroup.groups, generateCriteriaGroupForBinaryExpression((BinaryExpression) operandExp, context));
} else {
CorelatedCriteria corelatedCriteria = CorelatedCriteriaTranslator.generateCorelatedCriteriaForExpression(operandExp, context);
if (corelatedCriteria == null) {
throw new PhemaNotImplementedException("The translator was unable to process this type of expression");
}
criteriaGroup.criteriaList = CirceUtil.addCorelatedCriteria(criteriaGroup.criteriaList, corelatedCriteria);
}
}
}

return criteriaGroup;
}

/**
* This is the entry point for CriteriaGroup generation. This method will delegate CriteriaGroup generation
* based on the type of expression. An exception will be thrown if we do not support translating the expression
*
* @param expression The ELM expression
* @param context The translation context
* @return The created CriteriaGroup
* @throws Exception
*/
public static CriteriaGroup generateCriteriaGroupForExpression(Expression expression, PhemaElmaToOmopTranslatorContext context) throws Exception {
Expression expr = PhemaElmToOmopTranslator.invertNot(expression);

CriteriaGroup criteriaGroup = new CriteriaGroup();

if (DemographicExpressionTranslator.isDemographicExpression(expr)) {
DemographicCriteria demographicCriteria = DemographicExpressionTranslator.generateDemographicCriteriaForExpression(expr);
criteriaGroup.demographicCriteriaList = CirceUtil.addDemographicCriteria(criteriaGroup.demographicCriteriaList, demographicCriteria);
} else if (expr instanceof And || expr instanceof Or) {
return generateCriteriaGroupForBinaryExpression((BinaryExpression) expr, context);
} else {
CorelatedCriteria corelatedCriteria = CorelatedCriteriaTranslator.generateCorelatedCriteriaForExpression(expr, context);
criteriaGroup.criteriaList = CirceUtil.addCorelatedCriteria(criteriaGroup.criteriaList, corelatedCriteria);
}

return criteriaGroup;
}
}
Loading

0 comments on commit b52d863

Please sign in to comment.