omop-constructs 0.4.1__tar.gz → 0.4.2__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 (76) hide show
  1. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/PKG-INFO +1 -1
  2. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/pyproject.toml +1 -1
  3. omop_constructs-0.4.2/src/omop_constructs/alchemy/__init__.py +7 -0
  4. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/consult_window_mv.py +8 -0
  5. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/consult_window_query.py +14 -0
  6. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/dx_linked_visit_mv.py +9 -0
  7. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/visit_queries.py +8 -0
  8. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/modifier_joins.py +9 -7
  9. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/constructs.py +14 -2
  10. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/materialized.py +12 -0
  11. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/plan.py +9 -0
  12. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/registry.py +25 -1
  13. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/semantics/lookups.py +10 -1
  14. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/semantics/runtime_lookups.py +10 -1
  15. omop_constructs-0.4.2/src/omop_constructs/services/__init__.py +5 -0
  16. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/typing.py +10 -1
  17. omop_constructs-0.4.1/src/omop_constructs/alchemy/__init__.py +0 -0
  18. omop_constructs-0.4.1/src/omop_constructs/services/__init__.py +0 -0
  19. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/README.md +0 -0
  20. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/__init__.py +0 -0
  21. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/adapters.py +0 -0
  22. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/concepts/__init__.py +0 -0
  23. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/concepts/concept_mappers.py +0 -0
  24. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/concepts/concept_standardisation.py +0 -0
  25. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/conditions/__init__.py +0 -0
  26. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/conditions/condition_joins.py +0 -0
  27. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/conditions/condition_mappers.py +0 -0
  28. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/demography/__init__.py +0 -0
  29. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/demography/demography_matview.py +0 -0
  30. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/demography/demography_queries.py +0 -0
  31. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/__init__.py +0 -0
  32. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/condition_episode_mv.py +0 -0
  33. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/condition_episode_objects.py +0 -0
  34. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/course_joins.py +0 -0
  35. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/course_mv.py +0 -0
  36. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/cycle_join.py +0 -0
  37. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/cycle_mv.py +0 -0
  38. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/episode_factories.py +0 -0
  39. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/episode_joins.py +0 -0
  40. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/fraction_joins.py +0 -0
  41. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/fraction_mv.py +0 -0
  42. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/modality_intent_join.py +0 -0
  43. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/modality_intent_mv.py +0 -0
  44. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/surgical_joins.py +0 -0
  45. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/surgical_procedure_mv.py +0 -0
  46. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/systemic_treatment_joins.py +0 -0
  47. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/systemic_treatment_mv.py +0 -0
  48. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/treatment_envelope_mv.py +0 -0
  49. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/treatment_envelope_query.py +0 -0
  50. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/treatment_summary_joins.py +0 -0
  51. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/episodes/treatment_summary_mv.py +0 -0
  52. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/__init__.py +0 -0
  53. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/dx_linked_event_mv.py +0 -0
  54. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/dx_linked_obs_mv.py +0 -0
  55. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/dx_linked_procedure_mv.py +0 -0
  56. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/event_factories.py +0 -0
  57. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/event_queries.py +0 -0
  58. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/observation_queries.py +0 -0
  59. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/events/procedure_queries.py +0 -0
  60. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/__init__.py +0 -0
  61. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/condition_modifier_join.py +0 -0
  62. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/condition_modifier_mv.py +0 -0
  63. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/modifier_factories.py +0 -0
  64. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/modifier_mappers.py +0 -0
  65. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/procedure_modifier_joins.py +0 -0
  66. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/modifiers/procedure_modifier_mv.py +0 -0
  67. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/alchemy/source_factories.py +0 -0
  68. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/__init__.py +0 -0
  69. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/base.py +0 -0
  70. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/ddl.py +0 -0
  71. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/core/errors.py +0 -0
  72. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/py.typed +0 -0
  73. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/semantics/__init__.py +0 -0
  74. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/semantics/adapters.py +0 -0
  75. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/services/concept.py +0 -0
  76. {omop_constructs-0.4.1 → omop_constructs-0.4.2}/src/omop_constructs/services/person.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: omop-constructs
3
- Version: 0.4.1
3
+ Version: 0.4.2
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.4.1"
3
+ version = "0.4.2"
4
4
  description = "For building complex constructs on top of the omop-alchemy library."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,7 @@
1
+ """
2
+ High-level construct families built on top of ``omop-alchemy`` models.
3
+
4
+ The subpackages under :mod:`omop_constructs.alchemy` expose the repository's
5
+ active analytical constructs, mostly as ORM-mapped materialized views plus the
6
+ query fragments used to build them.
7
+ """
@@ -15,6 +15,13 @@ from .treatment_envelope_mv import TreatmentEnvelopeMV
15
15
 
16
16
  @register_construct
17
17
  class ConsultWindowMV(MaterializedViewMixin, Base):
18
+ """
19
+ Episode-of-care consult and referral window summary.
20
+
21
+ The materialized view exposes the earliest specialist contact and the
22
+ derived scalar windows currently used downstream for referral timing
23
+ analysis.
24
+ """
18
25
  __mv_name__ = "consult_window_mv"
19
26
  __mv_select__ = consult_window.select()
20
27
  __mv_index__ = "episode_id"
@@ -30,6 +37,7 @@ class ConsultWindowMV(MaterializedViewMixin, Base):
30
37
  person_id: so.Mapped[int] = so.mapped_column(sa.Integer)
31
38
  episode_id: so.Mapped[int] = so.mapped_column(sa.Integer)
32
39
  episode_start_date: so.Mapped[date] = so.mapped_column(sa.Date)
40
+ episode_concept_id: so.Mapped[int] = so.mapped_column(sa.Integer)
33
41
  initial_gp_referral: so.Mapped[Optional[date]] = so.mapped_column(
34
42
  sa.Date, nullable=True
35
43
  )
@@ -1,3 +1,11 @@
1
+ """
2
+ Episode-level consult and referral window query fragments.
3
+
4
+ The current implementation combines episode-of-care anchors, diagnosis-linked
5
+ consult observations, ranked specialist visits, and the treatment envelope to
6
+ derive referral-to-specialist and referral-to-treatment windows.
7
+ """
8
+
1
9
  import sqlalchemy as sa
2
10
  from sqlalchemy.sql import ColumnElement
3
11
 
@@ -13,6 +21,9 @@ def days_between(
13
21
  end_date: ColumnElement,
14
22
  start_date: ColumnElement,
15
23
  ) -> ColumnElement:
24
+ """
25
+ Return the interval between two date-like expressions in days.
26
+ """
16
27
  return sa.func.extract(
17
28
  "epoch",
18
29
  sa.cast(end_date, sa.DateTime) - sa.cast(start_date, sa.DateTime),
@@ -24,6 +35,7 @@ episode_of_care = (
24
35
  Episode.person_id,
25
36
  Episode.episode_id,
26
37
  Episode.episode_start_date,
38
+ Episode.episode_concept_id,
27
39
  )
28
40
  .where(
29
41
  Episode.episode_concept_id
@@ -141,6 +153,7 @@ specialist_trajectory = (
141
153
  episode_of_care.c.person_id,
142
154
  episode_of_care.c.episode_id,
143
155
  episode_of_care.c.episode_start_date,
156
+ episode_of_care.c.episode_concept_id,
144
157
  first_specialist.c.first_specialist_consult,
145
158
  first_specialist.c.last_specialist_consult,
146
159
  specialist_visit.c.first_specialist_visit,
@@ -183,6 +196,7 @@ consult_window = (
183
196
  specialist_trajectory.c.episode_id,
184
197
  specialist_trajectory.c.episode_start_date,
185
198
  specialist_trajectory.c.initial_gp_referral,
199
+ specialist_trajectory.c.episode_concept_id,
186
200
  sa.func.least(
187
201
  specialist_trajectory.c.first_specialist_consult,
188
202
  specialist_trajectory.c.first_specialist_visit,
@@ -11,6 +11,9 @@ from .visit_queries import dx_relevant_visits
11
11
 
12
12
 
13
13
  class ConditionEpisodeVisitCols:
14
+ """
15
+ Shared mapped columns for diagnosis-relevant visit materialized views.
16
+ """
14
17
  __table_args__ = {"extend_existing": True}
15
18
 
16
19
  mv_id: so.Mapped[int] = so.mapped_column(primary_key=True)
@@ -40,6 +43,12 @@ class ConditionEpisodeVisitCols:
40
43
 
41
44
  @register_construct
42
45
  class DxRelevantVisitMV(ConditionEpisodeVisitCols, MaterializedViewMixin, Base):
46
+ """
47
+ Provider-specialty visits linked to diagnosis episodes.
48
+
49
+ This view uses ranked episode assignment logic tailored to specialist visit
50
+ analysis rather than the generic event factory time-window attachment path.
51
+ """
43
52
  __mv_name__ = "dx_visit_mv"
44
53
  __mv_select__ = dx_relevant_visits.select()
45
54
  __mv_index__ = "person_id"
@@ -1,3 +1,11 @@
1
+ """
2
+ Query fragments for linking visit occurrences to disease episodes by specialty.
3
+
4
+ These queries support specialist consult-style measures where visit/provider
5
+ specialty is part of the derivation and generic event-attachment helpers are not
6
+ quite expressive enough.
7
+ """
8
+
1
9
  import sqlalchemy as sa
2
10
  import sqlalchemy.orm as so
3
11
 
@@ -1,14 +1,16 @@
1
+ """
2
+ Resolver-backed query fragments for staging and condition modifier selection.
3
+
4
+ Importing this module is not a pure no-op. At module import time it resolves the
5
+ configured semantics-backed resolver sets and uses them to build the stage and
6
+ modifier query fragments that feed the modifier materialized views.
7
+ """
8
+
1
9
  from omop_constructs.semantics import registry
2
10
  from omop_alchemy.cdm.model.clinical import Measurement
3
11
  from omop_semantics.runtime.default_valuesets import runtime
4
12
  from .modifier_factories import get_query_per_stage_type, get_eav_modifier_query, earliest_modifier, get_direct_modifier_query
5
- """
6
- These calls have side effects of building the resolvers and querying the database to
7
- cache the required concepts in memory, but this is necessary to create the correct
8
- subqueries for staging modifiers.
9
13
 
10
- TODO: we may want to refactor to separate out the resolver construction from the concept retrieval?
11
- """
12
14
  t_stage_select = get_query_per_stage_type(registry['tnm_t_stage'].all_concepts, name="t_stage")
13
15
  n_stage_select = get_query_per_stage_type(registry['tnm_n_stage'].all_concepts, name="n_stage")
14
16
  m_stage_select = get_query_per_stage_type(registry['tnm_m_stage'].all_concepts, name="m_stage")
@@ -50,4 +52,4 @@ all_stage_select = get_direct_modifier_query(
50
52
  )
51
53
  ),
52
54
  name="all_stage_modifiers"
53
- )
55
+ )
@@ -9,7 +9,11 @@ _CONSTRUCTS: Dict[str, Type[SupportsMaterializedView]] = {}
9
9
 
10
10
  def register_construct(cls: T) -> T:
11
11
  """
12
- Decorator to register a materialized view / construct class.
12
+ Register a materialized view-backed construct class.
13
+
14
+ Registration is import-driven: any module that defines construct classes and
15
+ imports this decorator will add those classes to the in-process registry as
16
+ soon as the module is imported.
13
17
  """
14
18
  name = getattr(cls, "__mv_name__", None)
15
19
  if not name:
@@ -24,4 +28,12 @@ def register_construct(cls: T) -> T:
24
28
  return cls
25
29
 
26
30
  def get_construct_registry() -> ConstructRegistry:
27
- return ConstructRegistry(_CONSTRUCTS.values())
31
+ """
32
+ Return a registry view over all construct classes imported so far.
33
+
34
+ Notes
35
+ -----
36
+ The returned registry reflects the current Python process. If a construct
37
+ module has not yet been imported, its classes will not appear here.
38
+ """
39
+ return ConstructRegistry(_CONSTRUCTS.values())
@@ -18,6 +18,9 @@ class MaterializedViewMixin:
18
18
 
19
19
  @classmethod
20
20
  def create_mv(cls, bind, *, with_data: bool = True) -> None:
21
+ """
22
+ Create the backing materialized view for this mapped class.
23
+ """
21
24
  ddl = CreateMaterializedView(cls.__mv_name__, cls.__mv_select__, with_data=with_data)
22
25
  bind.execute(ddl)
23
26
  if cls.__mv_index__ is not None:
@@ -25,6 +28,9 @@ class MaterializedViewMixin:
25
28
 
26
29
  @classmethod
27
30
  def refresh_mv(cls, bind, *, concurrently: bool = False):
31
+ """
32
+ Refresh the backing materialized view.
33
+ """
28
34
  suffix = " CONCURRENTLY" if concurrently else ""
29
35
  bind.execute(
30
36
  sa.text(f"REFRESH MATERIALIZED VIEW{suffix} {cls.__mv_name__}")
@@ -32,11 +38,17 @@ class MaterializedViewMixin:
32
38
 
33
39
  @classmethod
34
40
  def drop_mv(cls, bind, *, cascade: bool = False):
41
+ """
42
+ Drop the backing materialized view.
43
+ """
35
44
  ddl = DropMaterializedView(cls.__mv_name__, cascade=cascade)
36
45
  bind.execute(ddl)
37
46
 
38
47
  @classmethod
39
48
  def create_index(cls, bind, index_colname: str):
49
+ """
50
+ Create the configured helper index for the materialized view if needed.
51
+ """
40
52
  bind.execute(sa.text(
41
53
  f"CREATE INDEX IF NOT EXISTS {cls.__mv_name__}_{index_colname}_idx "
42
54
  f"ON {cls.__mv_name__} ({index_colname})"
@@ -7,11 +7,20 @@ from .errors import DependencyCycleError
7
7
 
8
8
  @dataclass(frozen=True)
9
9
  class ConstructNode:
10
+ """
11
+ Lightweight dependency node used when building construct execution plans.
12
+ """
10
13
  name: str
11
14
  deps: tuple[str, ...] = ()
12
15
  kind: str = "materialized_view"
13
16
 
14
17
  def topo_sort(nodes: list[ConstructNode]) -> list[ConstructNode]:
18
+ """
19
+ Topologically sort construct nodes by their declared dependencies.
20
+
21
+ Dependencies that point outside the provided node set are treated as
22
+ external and ignored for ordering purposes.
23
+ """
15
24
  by_name = {n.name: n for n in nodes}
16
25
  indeg = {n.name: 0 for n in nodes}
17
26
  adj = defaultdict(list)
@@ -9,12 +9,18 @@ from ..typing import SupportsMaterializedView
9
9
 
10
10
  @dataclass(frozen=True)
11
11
  class ConstructPlanItem:
12
+ """
13
+ Serializable view of a construct's name, kind, and declared dependencies.
14
+ """
12
15
  name: str
13
16
  kind: str
14
17
  deps: tuple[str, ...]
15
18
 
16
19
 
17
20
  def materialized_view_exists(bind, name: str, schema: str = "public") -> bool:
21
+ """
22
+ Check whether a PostgreSQL materialized view already exists.
23
+ """
18
24
  sql = sa.text("""
19
25
  SELECT EXISTS (
20
26
  SELECT 1
@@ -34,15 +40,24 @@ class ConstructRegistry:
34
40
  _constructs: dict[str, Type[SupportsMaterializedView]]
35
41
 
36
42
  def __init__(self, constructs: Iterable[type[SupportsMaterializedView]]):
43
+ """
44
+ Build a registry over an iterable of construct classes.
45
+ """
37
46
  self._constructs = {c.__mv_name__: c for c in constructs}
38
47
 
39
48
  def __iter__(self) -> Iterator[type[SupportsMaterializedView]]:
40
49
  return iter(self._constructs.values())
41
50
 
42
51
  def get(self, name: str) -> type[SupportsMaterializedView]:
52
+ """
53
+ Return a registered construct class by materialized view name.
54
+ """
43
55
  return self._constructs[name]
44
56
 
45
57
  def plan(self) -> tuple[ConstructPlanItem, ...]:
58
+ """
59
+ Return constructs in dependency order.
60
+ """
46
61
  nodes = [
47
62
  ConstructNode(name=k, deps=v.__deps__, kind=getattr(v.__construct__, "kind", "materialized_view"))
48
63
  for k, v in self._constructs.items()
@@ -51,6 +66,9 @@ class ConstructRegistry:
51
66
  return tuple(ConstructPlanItem(n.name, n.kind, n.deps) for n in ordered)
52
67
 
53
68
  def create_all(self, bind, *, with_data: bool = True):
69
+ """
70
+ Create every registered materialized view in dependency order.
71
+ """
54
72
  for item in self.plan():
55
73
  cls = self._constructs[item.name]
56
74
  cls.create_mv(bind, with_data=with_data)
@@ -60,6 +78,9 @@ class ConstructRegistry:
60
78
  self._constructs[item.name].refresh_mv(bind, concurrently=concurrently)
61
79
 
62
80
  def drop_all(self, bind, *, cascade: bool = False) -> None:
81
+ """
82
+ Drop every registered materialized view in reverse dependency order.
83
+ """
63
84
  # drop reverse order
64
85
  for item in reversed(self.plan()):
65
86
  try:
@@ -221,6 +242,9 @@ class ConstructRegistry:
221
242
 
222
243
 
223
244
  def describe(self, full: bool = False) -> str:
245
+ """
246
+ Render a text description of the registry dependency graph.
247
+ """
224
248
  try:
225
249
  items = self.plan()
226
250
  except Exception:
@@ -274,4 +298,4 @@ class ConstructRegistry:
274
298
  last_root = i == len(roots) - 1
275
299
  walk(root, "", last_root)
276
300
 
277
- return "\n".join(lines)
301
+ return "\n".join(lines)
@@ -14,6 +14,9 @@ from typing import Callable
14
14
  # as a pre-processing step
15
15
 
16
16
  def get_concept_resolver_registry(engine: sa.engine.Engine) -> ConceptResolverRegistry:
17
+ """
18
+ Create a resolver registry bound to the supplied SQLAlchemy engine.
19
+ """
17
20
  return ConceptResolverRegistry(engine)
18
21
 
19
22
 
@@ -25,6 +28,9 @@ def build_parent_resolver(
25
28
  corrections: list[Callable] = [],
26
29
  unknown_concept_id: int = UNKNOWN["generic"].concept_id
27
30
  ) -> ConceptResolver:
31
+ """
32
+ Build a resolver from one or more OMOP parent concepts.
33
+ """
28
34
  return make_concept_resolver(
29
35
  session,
30
36
  name=resolver_name,
@@ -40,9 +46,12 @@ def build_stage_resolver(
40
46
  parent_list: list[int],
41
47
  stage_name: str
42
48
  ) -> ConceptResolver:
49
+ """
50
+ Build a staging resolver with the standard TNM correction pipeline.
51
+ """
43
52
  return build_parent_resolver(
44
53
  session,
45
54
  resolver_name=f"tnm_{stage_name}_stage",
46
55
  parent_list=parent_list,
47
56
  corrections=[strip_uicc, make_stage],
48
- )
57
+ )
@@ -54,16 +54,25 @@ DEFAULT_RESOLVER_BUILDERS = {
54
54
  }
55
55
 
56
56
  def get_registry_engine(env_path: str = __file__):
57
+ """
58
+ Build the engine used for runtime resolver lookup.
59
+
60
+ If ``ENGINE`` is not already defined in the environment, the function asks
61
+ ``omop-alchemy`` to load environment configuration relative to ``env_path``.
62
+ """
57
63
  if os.environ.get("ENGINE") is None:
58
64
  load_environment(env_path)
59
65
  engine_string = get_engine_name()
60
66
  return sa.create_engine(engine_string, future=True, echo=False)
61
67
 
62
68
  def get_runtime_resolvers(engine: sa.Engine) -> ConceptResolverRegistry:
69
+ """
70
+ Return a resolver registry populated with the default resolver builders.
71
+ """
63
72
  resolver_registry = get_concept_resolver_registry(engine)
64
73
 
65
74
  # Register all known resolvers up-front (lazy build)
66
75
  for name, builder in DEFAULT_RESOLVER_BUILDERS.items():
67
76
  resolver_registry.register(name, builder)
68
77
 
69
- return resolver_registry
78
+ return resolver_registry
@@ -0,0 +1,5 @@
1
+ """
2
+ Small helper services used alongside the construct layer.
3
+
4
+ These are thin convenience functions rather than registered construct classes.
5
+ """
@@ -4,6 +4,9 @@ from .core.plan import ConstructNode, topo_sort
4
4
 
5
5
  @runtime_checkable
6
6
  class SupportsMaterializedView(Protocol):
7
+ """
8
+ Protocol for ORM classes backed by materialized view definitions.
9
+ """
7
10
  __mv_name__: str
8
11
  __mv_select__: sa.sql.Select
9
12
  __deps__: tuple[str, ...]
@@ -19,6 +22,9 @@ class SupportsMaterializedView(Protocol):
19
22
 
20
23
  @runtime_checkable
21
24
  class SupportsConstructRegistry(Protocol):
25
+ """
26
+ Protocol for registry-like lifecycle managers.
27
+ """
22
28
  def create_all(self, bind: Any) -> None: ...
23
29
  def refresh_all(
24
30
  self,
@@ -31,12 +37,15 @@ class SupportsConstructRegistry(Protocol):
31
37
 
32
38
  @runtime_checkable
33
39
  class SupportsSelectable(Protocol):
40
+ """
41
+ Protocol for objects that can emit a SQLAlchemy ``Select``.
42
+ """
34
43
  def select(self) -> sa.sql.Select: ...
35
44
 
36
45
 
37
46
  @runtime_checkable
38
47
  class SupportsQueryFactory(Protocol):
39
48
  """
40
- Factory that produces SQLAlchemy Selectables
49
+ Factory that produces SQLAlchemy ``Select`` objects.
41
50
  """
42
51
  def build(self) -> sa.sql.Select: ...