omop-constructs 0.2.6__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/PKG-INFO +1 -1
  2. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/pyproject.toml +1 -1
  3. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/episodes/__init__.py +4 -1
  4. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/episodes/condition_episode_mv.py +44 -20
  5. omop_constructs-0.2.8/src/omop_constructs/alchemy/episodes/condition_episode_objects.py +51 -0
  6. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/episodes/episode_factories.py +12 -16
  7. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/episodes/episode_joins.py +8 -3
  8. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/README.md +0 -0
  9. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/__init__.py +0 -0
  10. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/__init__.py +0 -0
  11. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/adapters.py +0 -0
  12. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/concepts/__init__.py +0 -0
  13. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/concepts/concept_mappers.py +0 -0
  14. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/concepts/concept_standardisation.py +0 -0
  15. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/conditions/__init__.py +0 -0
  16. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/conditions/condition_joins.py +0 -0
  17. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/conditions/condition_mappers.py +0 -0
  18. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/modifiers/__init__.py +0 -0
  19. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/modifiers/condition_modifier_join.py +0 -0
  20. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/modifiers/condition_modifier_mv.py +0 -0
  21. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/modifiers/modifier_factories.py +0 -0
  22. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/modifiers/modifier_joins.py +0 -0
  23. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/modifiers/modifier_mappers.py +0 -0
  24. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/alchemy/source_factories.py +0 -0
  25. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/__init__.py +0 -0
  26. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/base.py +0 -0
  27. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/constructs.py +0 -0
  28. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/ddl.py +0 -0
  29. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/errors.py +0 -0
  30. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/materialized.py +0 -0
  31. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/plan.py +0 -0
  32. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/core/registry.py +0 -0
  33. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/py.typed +0 -0
  34. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/semantics/__init__.py +0 -0
  35. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/semantics/adapters.py +0 -0
  36. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/semantics/lookups.py +0 -0
  37. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/semantics/runtime_lookups.py +0 -0
  38. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/services/__init__.py +0 -0
  39. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/services/concept.py +0 -0
  40. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/services/person.py +0 -0
  41. {omop_constructs-0.2.6 → omop_constructs-0.2.8}/src/omop_constructs/typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: omop-constructs
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: For building complex constructs on top of the omop-alchemy library.
5
5
  Author: Georgie Kennedy
6
6
  Author-email: Georgie Kennedy <georgina.kennedy@unsw.edu.au>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omop-constructs"
3
- version = "0.2.6"
3
+ version = "0.2.8"
4
4
  description = "For building complex constructs on top of the omop-alchemy library."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,13 +1,16 @@
1
- from .condition_episode_mv import OverarchingDiseaseEpisodeMV, TreatmentRegimenCycleMV
1
+ from .condition_episode_mv import OverarchingDiseaseEpisodeMV, TreatmentRegimenCycleMV, ConditionEpisodeMV
2
2
  from .episode_joins import episode_of_care_select, disease_extent_select, overarching_disease_episode
3
3
  from .episode_factories import get_episode_query, get_episode_hierarchy_query
4
+ from .condition_episode_objects import ConditionEpisodeObject
4
5
 
5
6
  __all__ = [
6
7
  "OverarchingDiseaseEpisodeMV",
7
8
  "TreatmentRegimenCycleMV",
9
+ "ConditionEpisodeMV",
8
10
  "episode_of_care_select",
9
11
  "disease_extent_select",
10
12
  "overarching_disease_episode",
11
13
  "get_episode_query",
12
14
  "get_episode_hierarchy_query",
15
+ "ConditionEpisodeObject",
13
16
  ]
@@ -3,12 +3,45 @@ import sqlalchemy.orm as so
3
3
  from orm_loader.helpers import Base
4
4
  from datetime import date
5
5
  from typing import Optional
6
- from .episode_joins import overarching_disease_episode, treatment_regimen_with_cycles
6
+ from .episode_joins import overarching_disease_episode, treatment_regimen_with_cycles, condition_episode_select
7
7
  from ...core.materialized import MaterializedViewMixin
8
8
  from ...core.constructs import register_construct
9
9
 
10
+ class DiseaseEpisodeCols:
11
+
12
+ disease_episode_id: so.Mapped[int] = so.mapped_column(sa.Integer, primary_key=True)
13
+ person_id: so.Mapped[int] = so.mapped_column(sa.Integer)
14
+
15
+ disease_episode_concept_id: so.Mapped[int] = so.mapped_column(sa.Integer)
16
+ disease_episode_label: so.Mapped[str] = so.mapped_column(sa.String)
17
+
18
+ disease_episode_start_date: so.Mapped[date] = so.mapped_column(sa.Date)
19
+ disease_episode_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
20
+
21
+ @register_construct
22
+ class ConditionEpisodeMV(
23
+ DiseaseEpisodeCols,
24
+ MaterializedViewMixin,
25
+ Base,
26
+ ):
27
+ """
28
+ Materialized view representing all disease episodes (irrespective of type).
29
+
30
+ Used for resolving linkages between clinical events and disease episodes
31
+ when there is no explicit link in the source data, and we must therefore use
32
+ time-windowing logic.
33
+ """
34
+ __mv_name__ = "condition_episode_mv"
35
+ __mv_select__ = condition_episode_select.select()
36
+ __mv_index__ = "disease_episode_id"
37
+ __deps__ = ()
38
+ __tablename__ = __mv_name__
39
+ __table_args__ = {"extend_existing": True}
40
+
41
+
10
42
  @register_construct
11
43
  class OverarchingDiseaseEpisodeMV(
44
+ DiseaseEpisodeCols,
12
45
  MaterializedViewMixin,
13
46
  Base,
14
47
  ):
@@ -16,7 +49,7 @@ class OverarchingDiseaseEpisodeMV(
16
49
  Materialized view representing overarching disease episodes
17
50
  with optional child disease-extent episodes.
18
51
 
19
- One row per (episode_of_care, disease_extent_episode?) pair.
52
+ One row per (episode_of_care, disease_extent_episode) pair.
20
53
  """
21
54
 
22
55
  __mv_name__ = "overarching_disease_episode_mv"
@@ -26,21 +59,12 @@ class OverarchingDiseaseEpisodeMV(
26
59
  __tablename__ = __mv_name__
27
60
  __table_args__ = {"extend_existing": True}
28
61
 
29
- disease_episode_id: so.Mapped[int] = so.mapped_column(sa.Integer, primary_key=True)
30
- person_id: so.Mapped[int] = so.mapped_column(sa.Integer)
31
-
32
- disease_episode_concept_id: so.Mapped[int] = so.mapped_column(sa.Integer)
33
- disease_episode_label: so.Mapped[str] = so.mapped_column(sa.String)
34
-
35
- disease_episode_start_date: so.Mapped[date] = so.mapped_column(sa.Date)
36
- disease_episode_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
37
-
38
62
  extent_episode_id: so.Mapped[Optional[int]] = so.mapped_column(sa.Integer, nullable=True)
39
63
  extent_episode_concept_id: so.Mapped[Optional[int]] = so.mapped_column(sa.Integer, nullable=True)
40
64
  extent_episode_label: so.Mapped[Optional[str]] = so.mapped_column(sa.String, nullable=True)
41
65
 
42
- extent_start_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
43
- extent_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
66
+ extent_episode_start_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
67
+ extent_episode_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
44
68
 
45
69
  def __repr__(self) -> str:
46
70
  if self.has_extent:
@@ -62,7 +86,7 @@ class OverarchingDiseaseEpisodeMV(
62
86
  def extent_interval(self):
63
87
  if self.extent_episode_id is None:
64
88
  return None
65
- return (self.extent_start_date, self.extent_end_date)
89
+ return (self.extent_episode_start_date, self.extent_episode_end_date)
66
90
 
67
91
  @register_construct
68
92
  class TreatmentRegimenCycleMV(
@@ -89,15 +113,15 @@ class TreatmentRegimenCycleMV(
89
113
  regimen_episode_concept_id: so.Mapped[int] = so.mapped_column(sa.Integer)
90
114
  regimen_episode_label: so.Mapped[str] = so.mapped_column(sa.String)
91
115
 
92
- regimen_start_date: so.Mapped[date] = so.mapped_column(sa.Date)
93
- regimen_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
116
+ regimen_episode_start_date: so.Mapped[date] = so.mapped_column(sa.Date)
117
+ regimen_episode_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
94
118
 
95
119
  cycle_episode_id: so.Mapped[Optional[int]] = so.mapped_column(sa.Integer, nullable=True)
96
120
  cycle_episode_concept_id: so.Mapped[Optional[int]] = so.mapped_column(sa.Integer, nullable=True)
97
121
  cycle_episode_label: so.Mapped[Optional[str]] = so.mapped_column(sa.String, nullable=True)
98
122
 
99
- cycle_start_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
100
- cycle_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
123
+ cycle_episode_start_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
124
+ cycle_episode_end_date: so.Mapped[Optional[date]] = so.mapped_column(sa.Date, nullable=True)
101
125
 
102
126
 
103
127
  def __repr__(self) -> str:
@@ -114,10 +138,10 @@ class TreatmentRegimenCycleMV(
114
138
 
115
139
  @property
116
140
  def regimen_interval(self):
117
- return (self.regimen_start_date, self.regimen_end_date)
141
+ return (self.regimen_episode_start_date, self.regimen_episode_end_date)
118
142
 
119
143
  @property
120
144
  def cycle_interval(self):
121
145
  if self.cycle_episode_id is None:
122
146
  return None
123
- return (self.cycle_start_date, self.cycle_end_date)
147
+ return (self.cycle_episode_start_date, self.cycle_episode_end_date)
@@ -0,0 +1,51 @@
1
+ import sqlalchemy as sa
2
+ import sqlalchemy.orm as so
3
+ from typing import List
4
+
5
+ from omop_alchemy.cdm.model.structural import Episode_Event
6
+ from ..modifiers import ModifiedCondition
7
+ from .condition_episode_mv import ConditionEpisodeMV # wherever you defined it
8
+ from omop_semantics.runtime.default_valuesets import runtime # type: ignore
9
+
10
+
11
+ class ConditionEpisodeObject(ConditionEpisodeMV):
12
+ """
13
+ Consumption-layer wrapper around ConditionEpisodeMV that exposes
14
+ relationships to Episode_Event and resolved Condition_Occurrence rows.
15
+ """
16
+
17
+ __tablename__ = ConditionEpisodeMV.__tablename__
18
+ __mapper_args__ = {"concrete": False}
19
+ # All Episode_Event rows for this episode
20
+ episode_events: so.Mapped[List[Episode_Event]] = so.relationship(
21
+ Episode_Event,
22
+ primaryjoin=ConditionEpisodeMV.disease_episode_id == so.foreign(Episode_Event.episode_id),
23
+ viewonly=True,
24
+ lazy="selectin",
25
+ )
26
+
27
+ # Only Condition_Occurrence-linked events
28
+ condition_events: so.Mapped[List[Episode_Event]] = so.relationship(
29
+ Episode_Event,
30
+ primaryjoin=sa.and_(
31
+ ConditionEpisodeMV.disease_episode_id == so.foreign(Episode_Event.episode_id),
32
+ Episode_Event.episode_event_field_concept_id
33
+ == runtime.modifiers.modifier_fields.condition_occurrence_id,
34
+ ),
35
+ viewonly=True,
36
+ lazy="selectin",
37
+ )
38
+
39
+ # Direct relationship to ModifiedCondition MV via Episode_Event spine
40
+ modified_conditions: so.Mapped[List[ModifiedCondition]] = so.relationship(
41
+ ModifiedCondition,
42
+ secondary=Episode_Event.__table__,
43
+ primaryjoin=ConditionEpisodeMV.disease_episode_id == so.foreign(Episode_Event.episode_id),
44
+ secondaryjoin=sa.and_(
45
+ so.foreign(Episode_Event.event_id) == ModifiedCondition.condition_occurrence_id,
46
+ Episode_Event.episode_event_field_concept_id
47
+ == runtime.modifiers.modifier_fields.condition_occurrence_id,
48
+ ),
49
+ viewonly=True,
50
+ lazy="selectin",
51
+ )
@@ -1,14 +1,10 @@
1
1
  import sqlalchemy as sa
2
2
  import sqlalchemy.orm as so
3
- from sqlalchemy.sql import ColumnElement
4
- from typing import Iterable, Any
3
+ from typing import Iterable
5
4
  from omop_alchemy.cdm.model import (
6
- Condition_Occurrence,
7
5
  Episode,
8
- Concept,
9
- Episode_Event
6
+ Concept
10
7
  )
11
- from omop_semantics.runtime.default_valuesets import runtime
12
8
 
13
9
  episode_concept = so.aliased(Concept, name="episode_concept")
14
10
 
@@ -48,8 +44,8 @@ def get_episode_hierarchy_query(
48
44
  parent_episode_subq: sa.Subquery,
49
45
  child_episode_subq: sa.Subquery,
50
46
  name: str = "episode_optional_children",
51
- parent_label: str = 'disease',
52
- child_label: str = 'extent'
47
+ parent_label: str = 'disease_episode',
48
+ child_label: str = 'extent_episode'
53
49
  ) -> sa.Subquery:
54
50
  """
55
51
  Expands an episode subquery to optionally include child episodes.
@@ -57,16 +53,16 @@ def get_episode_hierarchy_query(
57
53
 
58
54
  return (
59
55
  sa.select(
60
- parent_episode_subq.c.episode_id.label(f"{parent_label}_episode_id"),
56
+ parent_episode_subq.c.episode_id.label(f"{parent_label}_id"),
61
57
  parent_episode_subq.c.person_id.label("person_id"),
62
- parent_episode_subq.c.episode_concept_id.label(f"{parent_label}_episode_concept_id"),
63
- parent_episode_subq.c.episode_label.label(f"{parent_label}_episode_label"),
64
- parent_episode_subq.c.episode_start_date.label(f"{parent_label}_episode_start_date"),
65
- parent_episode_subq.c.episode_end_date.label(f"{parent_label}_episode_end_date"),
58
+ parent_episode_subq.c.episode_concept_id.label(f"{parent_label}_concept_id"),
59
+ parent_episode_subq.c.episode_label.label(f"{parent_label}_label"),
60
+ parent_episode_subq.c.episode_start_date.label(f"{parent_label}_start_date"),
61
+ parent_episode_subq.c.episode_end_date.label(f"{parent_label}_end_date"),
66
62
 
67
- child_episode_subq.c.episode_id.label(f"{child_label}_episode_id"),
68
- child_episode_subq.c.episode_concept_id.label(f"{child_label}_episode_concept_id"),
69
- child_episode_subq.c.episode_label.label(f"{child_label}_episode_label"),
63
+ child_episode_subq.c.episode_id.label(f"{child_label}_id"),
64
+ child_episode_subq.c.episode_concept_id.label(f"{child_label}_concept_id"),
65
+ child_episode_subq.c.episode_label.label(f"{child_label}_label"),
70
66
  child_episode_subq.c.episode_start_date.label(f"{child_label}_start_date"),
71
67
  child_episode_subq.c.episode_end_date.label(f"{child_label}_end_date"),
72
68
  )
@@ -1,5 +1,5 @@
1
+ from omop_semantics.runtime.default_valuesets import runtime # type: ignore
1
2
  from .episode_factories import get_episode_query, get_episode_hierarchy_query
2
- from omop_semantics.runtime.default_valuesets import runtime
3
3
 
4
4
  episode_of_care_select = get_episode_query(
5
5
  [runtime.types.disease_episode_types.episode_of_care], # type: ignore
@@ -17,6 +17,11 @@ overarching_disease_episode = get_episode_hierarchy_query(
17
17
  name="overarching_disease_episode",
18
18
  )
19
19
 
20
+ condition_episode_select = get_episode_query(
21
+ [runtime.types.disease_episode_types.disease_progression, runtime.types.disease_episode_types.metastatic, runtime.types.disease_episode_types.episode_of_care], # type: ignore
22
+ name="condition_episode",
23
+ )
24
+
20
25
  treatment_regimen_select = get_episode_query(
21
26
  [runtime.types.treatment_episode_types.treatment_regimen], # type: ignore
22
27
  name="treatment_regimen",
@@ -31,6 +36,6 @@ treatment_regimen_with_cycles = get_episode_hierarchy_query(
31
36
  parent_episode_subq=treatment_regimen_select,
32
37
  child_episode_subq=treatment_cycle_select,
33
38
  name="treatment_regimen_with_cycles",
34
- parent_label="regimen",
35
- child_label="cycle"
39
+ parent_label="regimen_episode",
40
+ child_label="cycle_episode"
36
41
  )