Architecture¶
Layering¶
The repository is organized into a small set of layers.
Core¶
omop_constructs.core handles:
- construct registration
- dependency planning
- materialized view DDL
- registry inspection and validation
This is the operational layer that turns mapped SQLAlchemy classes into something you can manage as a dependency graph.
Semantics¶
omop_constructs.semantics provides runtime concept resolver setup on top of omop-semantics value sets and omop-alchemy resolver helpers.
The default resolver set includes:
tnm_t_stagetnm_n_stagetnm_m_stagetnm_group_stagemetastatic_diseasetumor_gradert_procedurescountry_of_birth
Alchemy Construct Families¶
The omop_constructs.alchemy package contains the construct definitions themselves:
modifiersepisodeseventsdemographyconditions
These are mostly SQLAlchemy select() fragments plus ORM-mapped materialized view classes.
Registration Model¶
Construct classes are registered through @register_construct.
This has two consequences:
- registration happens at import time
- the registry is process-local and reflects what has been imported so far
That import-driven behavior is intentional and should be assumed by downstream consumers.
Dependency Planning¶
ConstructRegistry.plan() builds a DAG from each construct class's __deps__ tuple and topologically sorts it.
That ordering is reused for:
create_allrefresh_allcreate_missingrefresh_existing
and reversed for drop_all.
Dependencies can point at constructs outside the currently imported set; those are treated as external and skipped during sorting rather than rejected.
Event Attachment Strategy¶
The active event-linkage code centers on omop_constructs.alchemy.events.event_factories.
For procedures, measurements, and observations the library supports:
- explicit attachment via
Episode_Event - fallback time-window attachment to
ConditionEpisodeMV - post-filtering through
episode_relevant_window
This keeps downstream event MVs consistent:
- person identifier
- event identifier
- event date
- event concept metadata
- attached disease episode metadata
Visit Linkage¶
Visits are handled differently from observations and measurements.
DxRelevantVisitMV exposes provider-specialty visits linked to disease episodes.
It uses a ranked proximity approach rather than the generic event-factory time-window
attachment path:
- visits within ±180 days of the episode start (
episode_prior == 1) are always included - for each visit,
rank=1identifies its single highest-priority episode assignment, ordered by proximity tier then absolute day distance - multiple visits per episode appear as separate rows
- each row carries one atomic specialty concept — no specialty grouping occurs here
This design means a downstream measurable can filter DxRelevantVisitMV by
provider_specialty_concept_id and treat the result as an event stream, with all
grouping, de-duplication, and timing composition deferred to the measure engine.
Treatment Window And Consult Window Pattern¶
The episode layer exposes two important scalar-style constructs:
TreatmentEnvelopeMVearliest/latest treatment and treatment-derived scalar windowsConsultWindowMVreferral-derived specialist and treatment windows (scalar convenience layer — see below)
ConsultWindowMV is built by combining:
- episode-of-care anchors
- diagnosis-linked consult observations from
DxObservationMV - episode-linked provider-specialty visits from
DxRelevantVisitMV - earliest treatment from
TreatmentEnvelopeMV
ConsultWindowMV vs DxRelevantVisitMV¶
DxRelevantVisitMV |
ConsultWindowMV |
|
|---|---|---|
| Shape | one row per (visit, episode) | one scalar row per episode |
| Specialty | atomic concept per row | groups hardcoded specialty sets |
| Aggregation | none | min(visit_start_date) across specialty groups |
| Purpose | reusable event surface | oncology referral-timing scalars |
| Status | active, first-class | retained pending downstream migration |
ConsultWindowMV is a scalar convenience layer specific to oncology
referral-timing indicators. Its timing logic is superseded by the
temporal-window pattern in oa_cohorts. It must remain in place until
downstream measures complete migration; it should not be extended with new
specialty groups or timing variants.
Two-Measurable Temporal-Window Pattern¶
The preferred pattern for referral-timing indicators is:
- Define an observation measurable from
DxObservationMV— e.g. GP oncology referral (filtered byevent_concept_id) — as the anchor event. - Define one or more visit measurables from
DxRelevantVisitMV— e.g. one per specialist specialty concept — as candidate events. - Pass all candidate measurables to
measure_temporal_windowinoa_cohorts. The window engine takes the minimum over all candidates and computes the elapsed days against the anchor.
Example — "GP referral to first oncology specialist" indicator:
# Anchor: GP oncology referral observation
referral_obs = ObservationMeasurable(
construct=DxObservationMV,
person_id_attr="person_id",
episode_id_attr="episode_id",
event_date_attr="event_date",
value_concept_attr="event_concept_id",
concept_filter=[oncology_referral_concept_id],
)
# Candidates: specialist visits by specialty
medonc_visit = VisitMeasurable(
construct=DxRelevantVisitMV,
person_id_attr="person_id",
episode_id_attr="episode_id",
event_date_attr="visit_start_date",
value_concept_attr="provider_specialty_concept_id",
concept_filter=[medonc_concept_id],
)
radonc_visit = VisitMeasurable(
construct=DxRelevantVisitMV,
...
concept_filter=[radonc_concept_id],
)
haematology_visit = VisitMeasurable(
construct=DxRelevantVisitMV,
...
concept_filter=[haematologist_concept_id],
)
# Window engine (in oa_cohorts) combines candidates and measures against anchor
measure_temporal_window(
anchor=referral_obs,
candidates=[medonc_visit, radonc_visit, haematology_visit],
threshold_days=X,
)