Skip to content

Commit a7f6831

Browse files
committed
feat: add procedure action
1 parent a2764f3 commit a7f6831

File tree

6 files changed

+107
-82
lines changed

6 files changed

+107
-82
lines changed

execution_engine/builder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from execution_engine.converter.action.drug_administration import (
77
DrugAdministrationAction,
88
)
9+
from execution_engine.converter.action.procedure import ProcedureAction
910
from execution_engine.converter.action.ventilator_management import (
1011
VentilatorManagementAction,
1112
)
@@ -58,6 +59,7 @@ class CriterionConverterType(TypedDict):
5859
VentilatorManagementAction,
5960
BodyPositioningAction,
6061
AssessmentAction,
62+
ProcedureAction,
6163
],
6264
"goal": [LaboratoryValueGoal, VentilatorManagementGoal, AssessmentScaleGoal],
6365
"time_from_event": [],
Lines changed: 2 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
1-
from typing import Self, Type
2-
3-
from execution_engine.converter.action.abstract import AbstractAction
4-
from execution_engine.converter.criterion import parse_code
5-
from execution_engine.fhir.recommendation import RecommendationPlan
6-
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-
)
11-
from execution_engine.omop.criterion.concept import ConceptCriterion
12-
from execution_engine.omop.criterion.measurement import Measurement
13-
from execution_engine.omop.criterion.observation import Observation
14-
from execution_engine.omop.criterion.procedure_occurrence import ProcedureOccurrence
1+
from execution_engine.converter.action.procedure import ProcedureAction
152
from execution_engine.omop.vocabulary import SNOMEDCT
16-
from execution_engine.util.types import Timing
173

184

19-
class AssessmentAction(AbstractAction):
5+
class AssessmentAction(ProcedureAction):
206
"""
217
An AssessmentAction is an action that is used to assess a patient's condition.
228
@@ -26,65 +12,3 @@ class AssessmentAction(AbstractAction):
2612

2713
_concept_code = "386053000" # Evaluation procedure (procedure)
2814
_concept_vocabulary = SNOMEDCT
29-
30-
def __init__(
31-
self,
32-
exclude: bool,
33-
code: Concept,
34-
timing: Timing | None = None,
35-
) -> None:
36-
"""
37-
Initialize the assessment action.
38-
"""
39-
super().__init__(exclude=exclude)
40-
self._code = code
41-
self._timing = timing
42-
43-
@classmethod
44-
def from_fhir(cls, action_def: RecommendationPlan.Action) -> Self:
45-
"""Creates a new action from a FHIR PlanDefinition."""
46-
assert (
47-
action_def.activity_definition_fhir is not None
48-
), "ActivityDefinition is required"
49-
assert (
50-
action_def.activity_definition_fhir.code is not None
51-
), "Code is required for AssessmentAction"
52-
assert (
53-
action_def.activity_definition_fhir.timingTiming is not None
54-
), "Timing is required for AssessmentAction"
55-
56-
code = parse_code(action_def.activity_definition_fhir.code)
57-
timing = cls.process_timing(action_def.activity_definition_fhir.timingTiming)
58-
59-
exclude = action_def.activity_definition_fhir.doNotPerform
60-
61-
return cls(exclude=exclude, code=code, timing=timing)
62-
63-
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
64-
"""Converts this characteristic to a Criterion."""
65-
66-
cls: Type[ConceptCriterion]
67-
68-
match self._code.domain_id:
69-
case "Procedure":
70-
cls = ProcedureOccurrence
71-
case "Measurement":
72-
cls = Measurement
73-
case "Observation":
74-
cls = Observation
75-
case _:
76-
raise ValueError(
77-
f"Concept domain {self._code.domain_id} is not supported for AssessmentAction"
78-
)
79-
80-
criterion = cls(
81-
concept=self._code,
82-
timing=self._timing,
83-
)
84-
85-
# we need to explicitly set the VALUE_REQUIRED flag to false after creation of the object,
86-
# otherwise creating the query will raise an error as Observation and Measurement normally expect
87-
# a value.
88-
criterion._OMOP_VALUE_REQUIRED = False
89-
90-
return criterion
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from typing import Self, Type
2+
3+
from execution_engine.converter.action.abstract import AbstractAction
4+
from execution_engine.converter.criterion import parse_code
5+
from execution_engine.fhir.recommendation import RecommendationPlan
6+
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+
)
11+
from execution_engine.omop.criterion.concept import ConceptCriterion
12+
from execution_engine.omop.criterion.measurement import Measurement
13+
from execution_engine.omop.criterion.observation import Observation
14+
from execution_engine.omop.criterion.procedure_occurrence import ProcedureOccurrence
15+
from execution_engine.omop.vocabulary import SNOMEDCT
16+
from execution_engine.util.types import Timing
17+
18+
19+
class ProcedureAction(AbstractAction):
20+
"""
21+
An ProcedureAction is an action that describes a procedure to be performed
22+
23+
This action tests whether the procedure has been performed by determining whether it is
24+
is present in the respective OMOP CDM table.
25+
"""
26+
27+
_concept_code = "71388002" # Procedure (procedure)
28+
_concept_vocabulary = SNOMEDCT
29+
30+
def __init__(
31+
self,
32+
exclude: bool,
33+
code: Concept,
34+
timing: Timing | None = None,
35+
) -> None:
36+
"""
37+
Initialize the procedure action.
38+
"""
39+
super().__init__(exclude=exclude)
40+
self._code = code
41+
self._timing = timing
42+
43+
@classmethod
44+
def from_fhir(cls, action_def: RecommendationPlan.Action) -> Self:
45+
"""Creates a new action from a FHIR PlanDefinition."""
46+
assert (
47+
action_def.activity_definition_fhir is not None
48+
), f"ActivityDefinition is required for {cls.__class__.__name__}"
49+
assert (
50+
action_def.activity_definition_fhir.code is not None
51+
), f"Code is required for {cls.__class__.__name__}"
52+
assert (
53+
action_def.activity_definition_fhir.timingTiming is not None
54+
), f"Timing is required for {cls.__class__.__name__}"
55+
56+
code = parse_code(action_def.activity_definition_fhir.code)
57+
timing = cls.process_timing(action_def.activity_definition_fhir.timingTiming)
58+
59+
exclude = action_def.activity_definition_fhir.doNotPerform
60+
61+
return cls(exclude=exclude, code=code, timing=timing)
62+
63+
def _to_criterion(self) -> Criterion | LogicalCriterionCombination | None:
64+
"""Converts this characteristic to a Criterion."""
65+
66+
cls: Type[ConceptCriterion]
67+
68+
match self._code.domain_id:
69+
case "Procedure":
70+
cls = ProcedureOccurrence
71+
case "Measurement":
72+
cls = Measurement
73+
case "Observation":
74+
cls = Observation
75+
case _:
76+
raise ValueError(
77+
f"Concept domain {self._code.domain_id} is not supported for {self.__class__.__name__}]"
78+
)
79+
80+
criterion = cls(
81+
concept=self._code,
82+
timing=self._timing,
83+
)
84+
85+
# we need to explicitly set the VALUE_REQUIRED flag to false after creation of the object,
86+
# otherwise creating the query will raise an error as Observation and Measurement normally expect
87+
# a value.
88+
criterion._OMOP_VALUE_REQUIRED = False
89+
90+
return criterion

execution_engine/fhir/recommendation.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ def __init__(
182182
self._action: fhir.resources.plandefinition.PlanDefinitionAction = (
183183
action_def
184184
)
185-
self._activity: fhir.resources.activitydefinition.ActivityDefinition | None = (
186-
None
187-
)
185+
self._activity: (
186+
fhir.resources.activitydefinition.ActivityDefinition | None
187+
) = None
188188
self._package_version = package_version
189189

190190
# an action must not necessarily contain an activity definition (e.g. ventilator management)
@@ -234,6 +234,12 @@ def goals_fhir(
234234
"""Get the goals for this action."""
235235
return self._goals
236236

237+
def model_dump(self) -> dict:
238+
"""
239+
Return the FHIR PlanDefinitionAction model dump
240+
"""
241+
return self.fhir().model_dump()
242+
237243
def __init__(
238244
self, canonical_url: str, package_version: str, fhir_connector: FHIRClient
239245
):

execution_engine/omop/sqlclient.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ def get_concept(
328328
"""
329329
logging.info(f"Requesting standard concept: {vocabulary} #{code}")
330330

331+
if code is None:
332+
raise ValueError("Code must be set")
333+
331334
concept = omop.Concept.__table__.alias("c")
332335
query = concept.select().where(
333336
and_(

execution_engine/omop/vocabulary.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def get(self, system_uri: str) -> AbstractVocabulary:
232232
if system_uri in self._vocabulary:
233233
return self._vocabulary[system_uri]
234234
else:
235-
raise VocabularyNotFoundError(f"Vocabulary {system_uri} not found")
235+
raise VocabularyNotFoundError(f'Vocabulary "{system_uri}" not found')
236236

237237

238238
class StandardVocabulary:

0 commit comments

Comments
 (0)