Skip to content

Commit 0b26e6a

Browse files
committed
refactor: remove criterion combination
1 parent b3079ee commit 0b26e6a

37 files changed

+606
-1972
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[flake8]
2-
ignore = E203, E266, E501, W503, F403, F401, E402, C901, F405
2+
ignore = E203, E266, E501, W503, F403, F401, E402, C901, F405, E731
33
max-line-length = 88
44
max-complexity = 18
55
select = B,C,E,F,W,T4,B9

execution_engine/converter/action/abstract.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@
88
from execution_engine.fhir.recommendation import RecommendationPlan
99
from execution_engine.fhir.util import get_coding
1010
from execution_engine.omop.criterion.abstract import Criterion
11-
from execution_engine.omop.criterion.combination.logical import (
12-
LogicalCriterionCombination,
13-
)
1411
from execution_engine.omop.vocabulary import AbstractVocabulary
15-
from execution_engine.util import AbstractPrivateMethods
12+
from execution_engine.util import AbstractPrivateMethods, logic
1613
from execution_engine.util.types import Timing
1714
from execution_engine.util.value.time import ValueCount, ValueDuration, ValuePeriod
1815

@@ -144,27 +141,27 @@ def process_timing(cls, timing: FHIRTiming) -> Timing:
144141
)
145142

146143
@abstractmethod
147-
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
144+
def _to_expression(self) -> logic.BaseExpr | None:
148145
"""Converts this action to a Criterion."""
149146
raise NotImplementedError()
150147

151148
@final
152-
def to_positive_criterion(self) -> Criterion | LogicalCriterionCombination:
149+
def to_positive_expression(self) -> logic.BaseExpr:
153150
"""
154151
Converts this action to a criterion.
155152
"""
156-
action = self._to_criterion()
153+
action = self._to_expression()
157154

158155
if action is None:
159156
assert (
160157
self.goals
161158
), "Action without explicit criterion must have at least one goal"
162159

163160
if self.goals:
164-
criteria = [goal.to_criterion() for goal in self.goals]
161+
criteria = [goal.to_expression() for goal in self.goals]
165162
if action is not None:
166163
criteria.append(action)
167-
return LogicalCriterionCombination.And(*criteria)
164+
return logic.And(*criteria)
168165
else:
169166
return action # type: ignore
170167

execution_engine/converter/action/body_positioning.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@
44
from execution_engine.converter.criterion import parse_code
55
from execution_engine.fhir.recommendation import RecommendationPlan
66
from execution_engine.omop.concepts import Concept
7-
from execution_engine.omop.criterion.abstract import Criterion
8-
from execution_engine.omop.criterion.combination.logical import (
9-
LogicalCriterionCombination,
10-
)
117
from execution_engine.omop.criterion.procedure_occurrence import ProcedureOccurrence
128
from execution_engine.omop.vocabulary import SNOMEDCT
9+
from execution_engine.util import logic
1310
from execution_engine.util.types import Timing
1411

1512

@@ -48,10 +45,12 @@ def from_fhir(cls, action_def: RecommendationPlan.Action) -> Self:
4845

4946
return cls(exclude=exclude, code=code, timing=timing)
5047

51-
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
48+
def _to_expression(self) -> logic.Symbol:
5249
"""Converts this characteristic to a Criterion."""
5350

54-
return ProcedureOccurrence(
55-
concept=self._code,
56-
timing=self._timing,
51+
return logic.Symbol(
52+
criterion=ProcedureOccurrence(
53+
concept=self._code,
54+
timing=self._timing,
55+
)
5756
)

execution_engine/converter/action/drug_administration.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,14 @@
1515
)
1616
from execution_engine.fhir.recommendation import RecommendationPlan
1717
from execution_engine.omop.concepts import Concept
18-
from execution_engine.omop.criterion.abstract import Criterion
19-
from execution_engine.omop.criterion.combination.combination import CriterionCombination
20-
from execution_engine.omop.criterion.combination.logical import (
21-
LogicalCriterionCombination,
22-
NonCommutativeLogicalCriterionCombination,
23-
)
2418
from execution_engine.omop.criterion.drug_exposure import DrugExposure
2519
from execution_engine.omop.criterion.point_in_time import PointInTimeCriterion
2620
from execution_engine.omop.vocabulary import (
2721
SNOMEDCT,
2822
VocabularyFactory,
2923
standard_vocabulary,
3024
)
25+
from execution_engine.util import logic
3126
from execution_engine.util.types import Dosage
3227
from execution_engine.util.value import Value, ValueNumber
3328

@@ -278,26 +273,30 @@ def filter_same_unit(cls, df: pd.DataFrame, unit: Concept) -> pd.DataFrame:
278273

279274
return df_filtered
280275

281-
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
276+
def _to_expression(self) -> logic.BaseExpr:
282277
"""
283278
Returns a criterion that represents this action.
284279
"""
285280

286-
drug_actions: list[Criterion | LogicalCriterionCombination] = []
281+
drug_actions: list[logic.BaseExpr] = []
287282

288283
if not self._dosages:
289284
# no dosages, just return the drug exposure
290-
return DrugExposure(
291-
ingredient_concept=self._ingredient_concept,
292-
dose=None,
293-
route=None,
285+
return logic.Symbol(
286+
criterion=DrugExposure(
287+
ingredient_concept=self._ingredient_concept,
288+
dose=None,
289+
route=None,
290+
)
294291
)
295292

296293
for dosage in self._dosages:
297-
drug_action = DrugExposure(
298-
ingredient_concept=self._ingredient_concept,
299-
dose=dosage["dose"],
300-
route=dosage.get("route", None),
294+
drug_action = logic.Symbol(
295+
criterion=DrugExposure(
296+
ingredient_concept=self._ingredient_concept,
297+
dose=dosage["dose"],
298+
route=dosage.get("route", None),
299+
)
301300
)
302301

303302
extensions = dosage.get("extensions", None)
@@ -316,28 +315,29 @@ def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
316315
f"Extension type {extension['type']} not supported yet"
317316
)
318317

319-
ext_criterion = PointInTimeCriterion(
320-
concept=extension["code"],
321-
value=extension["value"],
318+
ext_criterion = logic.Symbol(
319+
criterion=PointInTimeCriterion(
320+
concept=extension["code"],
321+
value=extension["value"],
322+
)
322323
)
323324

324325
# A Conditional Filter returns `right` iff left is POSITIVE, otherwise it returns NEGATIVE
325326
# rational: "conditional" extensions are some conditions for dosage, such as body weight ranges.
326327
# Thus, the actual drug administration (drug_action, "right") must only be fulfilled if the
327328
# condition (ext_criterion, "left") is fulfilled. Thus, we here add this conditional filter.
328-
comb = NonCommutativeLogicalCriterionCombination.ConditionalFilter(
329+
comb = logic.ConditionalFilter(
329330
left=ext_criterion,
330331
right=drug_action,
331332
)
332333

333334
drug_actions.append(comb)
334335

335-
result: Criterion | CriterionCombination
336+
result: logic.BaseExpr
337+
336338
if len(drug_actions) == 1:
337339
result = drug_actions[0]
338340
else:
339-
result = LogicalCriterionCombination(
340-
operator=LogicalCriterionCombination.Operator("OR"),
341-
)
342-
result.add_all(drug_actions)
341+
result = logic.Or(*drug_actions)
342+
343343
return result

execution_engine/converter/action/procedure.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
from execution_engine.fhir.recommendation import RecommendationPlan
44
from execution_engine.omop.concepts import Concept
55
from execution_engine.omop.criterion.abstract import Criterion
6-
from execution_engine.omop.criterion.combination.logical import (
7-
LogicalCriterionCombination,
8-
)
96
from execution_engine.omop.criterion.measurement import Measurement
107
from execution_engine.omop.criterion.observation import Observation
118
from execution_engine.omop.criterion.procedure_occurrence import ProcedureOccurrence
129
from execution_engine.omop.vocabulary import SNOMEDCT
10+
from execution_engine.util import logic
1311
from execution_engine.util.types import Timing
1412

1513

@@ -57,7 +55,7 @@ def from_fhir(cls, action_def: RecommendationPlan.Action) -> AbstractAction:
5755

5856
return cls(exclude=exclude, code=code, timing=timing)
5957

60-
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
58+
def _to_expression(self) -> logic.Symbol:
6159
"""Converts this characteristic to a Criterion."""
6260

6361
criterion: Criterion
@@ -89,4 +87,4 @@ def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
8987
f"Concept domain {self._code.domain_id} is not supported for {self.__class__.__name__}]"
9088
)
9189

92-
return criterion
90+
return logic.Symbol(criterion=criterion)

execution_engine/converter/action/ventilator_management.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
from execution_engine.converter.action.abstract import AbstractAction
44
from execution_engine.fhir.recommendation import RecommendationPlan
5-
from execution_engine.omop.criterion.abstract import Criterion
6-
from execution_engine.omop.criterion.combination.logical import (
7-
LogicalCriterionCombination,
8-
)
95
from execution_engine.omop.vocabulary import SNOMEDCT
106

117

@@ -29,6 +25,6 @@ def from_fhir(cls, action_def: RecommendationPlan.Action) -> Self:
2925
exclude=False,
3026
) # fixme: no way to exclude goals (e.g. "do not ventilate")
3127

32-
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
28+
def _to_expression(self) -> None:
3329
"""Converts this characteristic to a Criterion."""
3430
return None

execution_engine/converter/characteristic/abstract.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from execution_engine.omop.concepts import Concept
99
from execution_engine.omop.criterion.abstract import Criterion
1010
from execution_engine.omop.vocabulary import standard_vocabulary
11+
from execution_engine.util import logic as logic
1112
from execution_engine.util.value import Value
1213

1314

@@ -30,7 +31,7 @@ class AbstractCharacteristic(CriterionConverter, ABC):
3031
Subclasses must define the following methods:
3132
- valid: returns True if the supplied characteristic falls within the scope of the subclass
3233
- from_fhir: creates a new instance of the subclass from a FHIR EvidenceVariable.characteristic element
33-
- to_criterion(): converts the characteristic to a Criterion
34+
- to_expression(): converts the characteristic to a Criterion
3435
"""
3536

3637
_criterion_class: Type[Criterion]
@@ -82,6 +83,13 @@ def valid(
8283
"""Checks if the given FHIR EvidenceVariable is a valid characteristic."""
8384
raise NotImplementedError()
8485

86+
@abstractmethod
87+
def to_positive_expression(self) -> logic.BaseExpr:
88+
"""
89+
Converts this characteristic to a positive expression (i.e. neglecting the exlude flag).
90+
"""
91+
raise NotImplementedError()
92+
8593
@staticmethod
8694
def get_standard_concept(cc: Coding) -> Concept:
8795
"""
@@ -95,13 +103,3 @@ def get_concept(cc: Coding, standard: bool = True) -> Concept:
95103
Get the OMOP Standard Vocabulary standard concept for the given code in the given vocabulary.
96104
"""
97105
return standard_vocabulary.get_concept(cc.system, cc.code, standard=standard)
98-
99-
@abstractmethod
100-
def to_positive_criterion(self) -> Criterion:
101-
"""
102-
Converts this characteristic to a "Positive" Criterion.
103-
104-
Positive criterion means that a possible excluded flag is disregarded. Instead, the exclusion
105-
is later introduced (in the to_criterion() method) via a LogicalCriterionCombination.Not).
106-
"""
107-
raise NotImplementedError()

execution_engine/converter/characteristic/codeable_concept.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from execution_engine.converter.characteristic.abstract import AbstractCharacteristic
77
from execution_engine.fhir.util import get_coding
88
from execution_engine.omop.concepts import Concept
9-
from execution_engine.omop.criterion.abstract import Criterion
109
from execution_engine.omop.criterion.concept import ConceptCriterion
1110
from execution_engine.omop.vocabulary import AbstractVocabulary
11+
from execution_engine.util import logic
1212

1313

1414
class AbstractCodeableConceptCharacteristic(AbstractCharacteristic):
@@ -78,10 +78,12 @@ def from_fhir(
7878

7979
return c
8080

81-
def to_positive_criterion(self) -> Criterion:
81+
def to_positive_expression(self) -> logic.BaseExpr:
8282
"""Converts this characteristic to a Criterion."""
83-
return self._criterion_class(
84-
concept=self.value,
85-
value=None,
86-
static=self._concept_value_static,
83+
return logic.Symbol(
84+
criterion=self._criterion_class(
85+
concept=self.value,
86+
value=None,
87+
static=self._concept_value_static,
88+
)
8789
)

execution_engine/converter/characteristic/value.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from execution_engine.converter.characteristic.abstract import AbstractCharacteristic
77
from execution_engine.converter.criterion import parse_code, parse_value
88
from execution_engine.omop.criterion.concept import ConceptCriterion
9+
from execution_engine.util import logic
910

1011

1112
class AbstractValueCharacteristic(AbstractCharacteristic, ABC):
@@ -32,10 +33,12 @@ def from_fhir(
3233

3334
return c
3435

35-
def to_positive_criterion(self) -> ConceptCriterion:
36+
def to_positive_expression(self) -> logic.Symbol:
3637
"""Converts this characteristic to a Criterion."""
37-
return self._criterion_class(
38-
concept=self.type,
39-
value=self.value,
40-
static=self._concept_value_static,
38+
return logic.Symbol(
39+
criterion=self._criterion_class(
40+
concept=self.type,
41+
value=self.value,
42+
static=self._concept_value_static,
43+
)
4144
)

execution_engine/converter/criterion.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from abc import ABC, abstractmethod
3-
from typing import Tuple, Type
3+
from typing import Tuple, Type, final
44

55
from fhir.resources.codeableconcept import CodeableConcept
66
from fhir.resources.element import Element
@@ -11,11 +11,8 @@
1111

1212
from execution_engine.fhir.util import get_coding
1313
from execution_engine.omop.concepts import Concept
14-
from execution_engine.omop.criterion.abstract import Criterion
15-
from execution_engine.omop.criterion.combination.logical import (
16-
LogicalCriterionCombination,
17-
)
1814
from execution_engine.omop.vocabulary import standard_vocabulary
15+
from execution_engine.util import logic as logic
1916
from execution_engine.util.value import Value, ValueConcept, ValueNumber
2017
from execution_engine.util.value.value import ValueScalar
2118

@@ -186,22 +183,23 @@ def valid(cls, fhir_definition: Element) -> bool:
186183
raise NotImplementedError()
187184

188185
@abstractmethod
189-
def to_positive_criterion(self) -> Criterion | LogicalCriterionCombination:
186+
def to_positive_expression(self) -> logic.BaseExpr:
190187
"""Converts this characteristic to a Criterion or a combination of criteria but no negation."""
191188
raise NotImplementedError()
192189

193-
def to_criterion(self) -> Criterion | LogicalCriterionCombination:
190+
@final
191+
def to_expression(self) -> logic.BaseExpr:
194192
"""
195-
Converts this characteristic to a Criterion or a
196-
combination of criteria. The result may be a "negative"
197-
criterion, that is the result of to_positive_criterion wrapped
198-
in a LogicalCriterionCombination with operator NOT.
193+
Converts this characteristic to an expression. The result may be a "negative"
194+
criterion, that is the result of to_positive_expression wrapped
195+
in a logic.Not.
199196
"""
200-
positive_criterion = self.to_positive_criterion()
197+
positive_expression = self.to_positive_expression()
198+
201199
if self._exclude:
202-
return LogicalCriterionCombination.Not(positive_criterion)
200+
return logic.Not(positive_expression)
203201
else:
204-
return positive_criterion
202+
return positive_expression
205203

206204

207205
class CriterionConverterFactory:

0 commit comments

Comments
 (0)