omop-constructs 0.5.2__tar.gz → 0.5.4__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 (81) hide show
  1. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/PKG-INFO +1 -1
  2. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/pyproject.toml +1 -1
  3. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/modality_intent_join.py +9 -11
  4. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/surgical_joins.py +45 -45
  5. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/README.md +0 -0
  6. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/__init__.py +0 -0
  7. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/_lazy_imports.py +0 -0
  8. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/__init__.py +0 -0
  9. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/adapters.py +0 -0
  10. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/concepts/__init__.py +0 -0
  11. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/concepts/concept_mappers.py +0 -0
  12. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/concepts/concept_standardisation.py +0 -0
  13. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/conditions/__init__.py +0 -0
  14. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/conditions/condition_joins.py +0 -0
  15. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/conditions/condition_mappers.py +0 -0
  16. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/demography/__init__.py +0 -0
  17. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/demography/demography_matview.py +0 -0
  18. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/demography/demography_queries.py +0 -0
  19. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/__init__.py +0 -0
  20. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/condition_episode_mv.py +0 -0
  21. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/condition_episode_objects.py +0 -0
  22. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/consult_window_mv.py +0 -0
  23. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/consult_window_query.py +0 -0
  24. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/course_joins.py +0 -0
  25. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/course_mv.py +0 -0
  26. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/cycle_join.py +0 -0
  27. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/cycle_mv.py +0 -0
  28. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/episode_factories.py +0 -0
  29. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/episode_joins.py +0 -0
  30. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/fraction_joins.py +0 -0
  31. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/fraction_mv.py +0 -0
  32. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/modality_intent_mv.py +0 -0
  33. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/surgical_procedure_mv.py +0 -0
  34. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/systemic_treatment_joins.py +0 -0
  35. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/systemic_treatment_mv.py +0 -0
  36. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/treatment_envelope_mv.py +0 -0
  37. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/treatment_envelope_query.py +0 -0
  38. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/treatment_summary_joins.py +0 -0
  39. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/episodes/treatment_summary_mv.py +0 -0
  40. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/__init__.py +0 -0
  41. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/dx_linked_event_mv.py +0 -0
  42. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/dx_linked_obs_mv.py +0 -0
  43. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/dx_linked_procedure_mv.py +0 -0
  44. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/dx_linked_visit_mv.py +0 -0
  45. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/event_factories.py +0 -0
  46. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/event_queries.py +0 -0
  47. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/observation_queries.py +0 -0
  48. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/procedure_queries.py +0 -0
  49. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/events/visit_queries.py +0 -0
  50. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/__init__.py +0 -0
  51. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/condition_modifier_join.py +0 -0
  52. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/condition_modifier_mv.py +0 -0
  53. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/modifier_factories.py +0 -0
  54. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/modifier_joins.py +0 -0
  55. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/modifier_mappers.py +0 -0
  56. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/procedure_modifier_joins.py +0 -0
  57. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/modifiers/procedure_modifier_mv.py +0 -0
  58. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/alchemy/source_factories.py +0 -0
  59. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/bootstrap.py +0 -0
  60. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/bootstrap_modules.py +0 -0
  61. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/cli.py +0 -0
  62. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/config.py +0 -0
  63. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/__init__.py +0 -0
  64. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/base.py +0 -0
  65. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/constructs.py +0 -0
  66. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/ddl.py +0 -0
  67. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/errors.py +0 -0
  68. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/materialized.py +0 -0
  69. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/plan.py +0 -0
  70. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/registry.py +0 -0
  71. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/schema_snapshot.py +0 -0
  72. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/core/sql.py +0 -0
  73. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/py.typed +0 -0
  74. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/semantics/__init__.py +0 -0
  75. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/semantics/adapters.py +0 -0
  76. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/semantics/lookups.py +0 -0
  77. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/semantics/runtime_lookups.py +0 -0
  78. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/services/__init__.py +0 -0
  79. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/services/concept.py +0 -0
  80. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/src/omop_constructs/services/person.py +0 -0
  81. {omop_constructs-0.5.2 → omop_constructs-0.5.4}/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.5.2
3
+ Version: 0.5.4
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.5.2"
3
+ version = "0.5.4"
4
4
  description = "For building complex constructs on top of the omop-alchemy library."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,10 +1,8 @@
1
1
  import sqlalchemy as sa
2
- import sqlalchemy.orm as so
3
- from omop_alchemy.cdm.model import Episode, Episode_Event, Measurement, Drug_Exposure, Procedure_Occurrence, Concept
2
+ from omop_alchemy.cdm.model import Episode, Episode_Event, Drug_Exposure, Procedure_Occurrence
4
3
  from omop_semantics.runtime.default_valuesets import runtime
5
4
  from ...semantics import registry
6
-
7
- intent_concept = so.aliased(Concept, name='intent_concept')
5
+ from ..modifiers.procedure_modifier_mv import ModifiedProcedure
8
6
 
9
7
  modality_sact = (
10
8
  sa.select(
@@ -47,19 +45,19 @@ episode_intent = (
47
45
  Episode.episode_start_date,
48
46
  Episode.episode_end_date,
49
47
  Episode.episode_parent_id,
50
- Measurement.measurement_concept_id,
51
- intent_concept.concept_name,
48
+ ModifiedProcedure.intent_concept_id.label('measurement_concept_id'),
49
+ ModifiedProcedure.intent_concept.label('concept_name'),
52
50
  )
53
51
  .join(
54
- Measurement,
52
+ Episode_Event,
55
53
  sa.and_(
56
- Measurement.measurement_event_id==Episode.episode_id,
57
- Measurement.meas_event_field_concept_id==runtime.modifiers.modifier_fields.episode_id
54
+ Episode_Event.episode_id==Episode.episode_id,
55
+ Episode_Event.episode_event_field_concept_id==runtime.modifiers.modifier_fields.procedure_occurrence_id
58
56
  )
59
57
  )
60
- .join(intent_concept, intent_concept.concept_id==Measurement.measurement_concept_id)
58
+ .join(ModifiedProcedure, ModifiedProcedure.procedure_occurrence_id==Episode_Event.event_id)
61
59
  .filter(
62
- Measurement.measurement_concept_id.in_(runtime.treatment_modifiers.treatment_intent.ids)
60
+ ModifiedProcedure.intent_concept_id.in_(runtime.treatment_modifiers.treatment_intent.ids)
63
61
  )
64
62
  .subquery()
65
63
  )
@@ -1,6 +1,7 @@
1
1
  import sqlalchemy as sa
2
2
  import sqlalchemy.orm as so
3
3
  from omop_alchemy.cdm.model import Concept, Concept_Ancestor, Procedure_Occurrence, Observation
4
+ from omop_alchemy.cdm.model.structural import Episode_Event
4
5
  from omop_semantics.runtime.default_valuesets import runtime # type: ignore
5
6
  from .condition_episode_mv import ConditionEpisodeMV
6
7
  from ..events.event_factories import (
@@ -84,10 +85,6 @@ radioisotopes_only = radioisotope_concepts.subquery(name="radioisotopes_only")
84
85
  surg_obs_concept = so.aliased(Concept, name="surg_obs_concept")
85
86
 
86
87
  # Stream 1: active surgical procedures from Procedure_Occurrence.
87
- # Note: these records are NOT registered in Episode_Event at this time.
88
- # Episode attachment must therefore use date windowing rather than
89
- # explicit Episode_Event joins - TODO: confirm if we push episode linkage
90
- # as its own distinct phase / utility?
91
88
  surgical_procedure_events = (
92
89
  sa.select(
93
90
  Procedure_Occurrence.person_id,
@@ -138,22 +135,14 @@ all_cancer_relevant_surg = (
138
135
  # here. historical_surgical_events are excluded because their timestamps do not
139
136
  # reliably represent the date of surgical treatment within the current episode.
140
137
  #
141
- # Episode attachment uses a date window because no Episode_Event records exist
142
- # for surgical Procedure_Occurrence rows (confirmed: pipeline step 08 writes
143
- # them directly without episode registration).
138
+ # Surgery attribution is now driven by explicit Episode_Event links created by
139
+ # pipeline-runtime. This makes the runtime the authority for choosing a single
140
+ # diagnosis episode per surgery, avoiding the former person+window fan-out
141
+ # across overlapping primaries.
144
142
  #
145
- # Window bounds (shared with event_factories defaults):
146
- # - Look back DEFAULT_EPISODE_WINDOW_DAYS_PRIOR days before episode start
147
- # to capture surgeries performed just before a formal diagnosis is coded
148
- # (e.g. diagnostic/staging surgery).
149
- # - Look forward to the episode end date when one is present, signalling an
150
- # explicitly closed episode. For open-ended episodes, allow up to
151
- # DEFAULT_EPISODE_OPEN_END_FALLBACK_DAYS days after episode start.
152
- #
153
- # A surgery that falls outside this window for every condition episode the
154
- # patient has will produce no row in SurgicalProcedureMV for that surgery.
155
- # That is the correct behaviour — it means the surgery cannot be attributed
156
- # to any known condition episode.
143
+ # SurgicalProcedureMV must still keep ConditionEpisodeMV as the spine so that
144
+ # episodes with no linked surgery produce a single null-valued row. oa-cohorts
145
+ # absence rules (e.g. "no surgery") depend on that outer-join shape.
157
146
 
158
147
  # surgical_procedure_events is kept as a Select (not a subquery) so it can be
159
148
  # passed directly to sa.union_all() in all_cancer_relevant_surg above. For the
@@ -161,51 +150,62 @@ all_cancer_relevant_surg = (
161
150
  # here. Both refer to the same query; the distinction is purely structural.
162
151
  _surg_proc_sq = surgical_procedure_events.subquery(name="surgical_procedure_events")
163
152
 
164
- _episode_end_bound = sa.func.coalesce(
165
- ConditionEpisodeMV.episode_end_date,
166
- ConditionEpisodeMV.episode_start_date + DEFAULT_EPISODE_OPEN_END_FALLBACK_DAYS,
153
+ linked_surgical_procedure_events = (
154
+ sa.select(
155
+ Episode_Event.episode_id.label("condition_episode_id"),
156
+ _surg_proc_sq.c.person_id,
157
+ _surg_proc_sq.c.surgery_occurrence_id,
158
+ _surg_proc_sq.c.surgery_concept_id,
159
+ _surg_proc_sq.c.surgery_datetime,
160
+ _surg_proc_sq.c.surgery_concept_code,
161
+ _surg_proc_sq.c.surgery_name,
162
+ _surg_proc_sq.c.surgery_source,
163
+ )
164
+ .select_from(Episode_Event)
165
+ .join(
166
+ _surg_proc_sq,
167
+ _surg_proc_sq.c.surgery_occurrence_id == Episode_Event.event_id,
168
+ )
169
+ .where(
170
+ Episode_Event.episode_event_field_concept_id
171
+ == runtime.modifiers.modifier_fields.procedure_occurrence_id
172
+ )
173
+ .subquery(name="linked_surgical_procedure_events")
167
174
  )
168
175
 
169
- # Cast surgery_datetime to Date for window comparison. Procedure_Occurrence
170
- # carries a full timestamp (time of surgery) but episode bounds are stored as
171
- # dates. Casting strips the time component and avoids fractional-day edge
172
- # cases where a surgery at 23:59 on the boundary day would otherwise be
173
- # excluded.
174
- _surgery_date = sa.cast(_surg_proc_sq.c.surgery_datetime, sa.Date)
175
-
176
176
  cancer_relevant_surg_select = (
177
177
  sa.select(
178
178
  sa.func.row_number().over().label("mv_id"),
179
179
  ConditionEpisodeMV.person_id,
180
180
  ConditionEpisodeMV.episode_id.label("condition_episode_id"),
181
181
  ConditionEpisodeMV.episode_start_date.label("condition_start_date"),
182
- _surg_proc_sq.c.surgery_occurrence_id,
183
- _surg_proc_sq.c.surgery_concept_id,
184
- _surg_proc_sq.c.surgery_datetime,
185
- _surg_proc_sq.c.surgery_concept_code,
186
- _surg_proc_sq.c.surgery_name,
187
- _surg_proc_sq.c.surgery_source,
182
+ linked_surgical_procedure_events.c.surgery_occurrence_id,
183
+ linked_surgical_procedure_events.c.surgery_concept_id,
184
+ linked_surgical_procedure_events.c.surgery_datetime,
185
+ linked_surgical_procedure_events.c.surgery_concept_code,
186
+ linked_surgical_procedure_events.c.surgery_name,
187
+ linked_surgical_procedure_events.c.surgery_source,
188
188
  )
189
189
  .select_from(ConditionEpisodeMV)
190
190
  .join(
191
- _surg_proc_sq,
192
- sa.and_(
193
- _surg_proc_sq.c.person_id == ConditionEpisodeMV.person_id,
194
- _surgery_date >= ConditionEpisodeMV.episode_start_date - DEFAULT_EPISODE_WINDOW_DAYS_PRIOR,
195
- _surgery_date <= _episode_end_bound,
196
- ),
191
+ linked_surgical_procedure_events,
192
+ linked_surgical_procedure_events.c.condition_episode_id == ConditionEpisodeMV.episode_id,
197
193
  # Outer join: every condition episode must appear in SurgicalProcedureMV,
198
- # including those with no surgery in the date window. oa-cohorts absence rules
194
+ # including those with no linked surgery. oa-cohorts absence rules
199
195
  # (e.g. "no surgery") rely on WHERE surgery_concept_id IS NULL to identify
200
196
  # non-surgical episodes — those rows only exist because of this outer join.
201
- # Episodes WITH surgery in the window still get one row per matched surgery
202
- # (the date window prevents the old person-level fan-out); episodes WITHOUT
203
- # surgery get exactly one row with all surgery columns NULL.
197
+ # Episodes WITH linked surgery still get one row per linked surgery;
198
+ # episodes WITHOUT surgery get exactly one row with all surgery columns NULL.
204
199
  isouter=True,
205
200
  )
206
201
  .subquery(name="cancer_relevant_surg")
207
202
  )
208
203
 
204
+ _episode_end_bound = sa.func.coalesce(
205
+ ConditionEpisodeMV.episode_end_date,
206
+ ConditionEpisodeMV.episode_start_date + DEFAULT_EPISODE_OPEN_END_FALLBACK_DAYS,
207
+ )
208
+
209
209
 
210
210
  # ---------------------------------------------------------------------------
211
211
  # Radioisotope procedures — separate construct, included here for proximity