episodes¶
Domain-neutral machinery for building episodes and retrieving what belongs to them. A
drug episode behaves the same whether the drug is a cytotoxic agent or an antibiotic, so
everything here takes concept filters and grouping keys as parameters rather than
assuming a clinical specialty. Domain-specific episode classes — for example
OncologyEpisode — compose these pieces with their own concept sets and live in
analytics.
derivation¶
How episodes are constructed and related to one another — building episode queries and
resolving parent/child hierarchy, written against the raw Episode/Episode_Event
tables rather than any materialised view.
Not yet populated. The equivalent built against materialised-view subclasses lives in
omop-constructs.
handling¶
What is inside an episode once it exists.
Linked drug exposures. DrugEpisodeMixin adds retrieval and grouped summaries to
any episode view:
from omop_alchemy.toolkit.episodes.handling import DrugEpisodeMixin
class MyEpisode(DrugEpisodeMixin, EpisodeView):
_drug_concept_ids = my_concept_ids
episode.drug_exposures # resolved Drug_Exposure rows
episode.drug_exposure_summaries_by() # grouped by drug concept by default
Construct a summary for an already selected set of rows through the summary type itself:
from omop_alchemy.toolkit.episodes.handling import DrugExposureSummary
summary = DrugExposureSummary.from_exposures(exposures, group_key="regimen-a")
Dose quantities are frequently not comparable across agents, because source units and
quantities arrive unnormalised. DoseEvaluability carries that judgement alongside the
number, so a summary that cannot be interpreted as a dose says so rather than presenting
a misleading total.
Explicit links versus admitted-by-window. Facts linked through Episode_Event are
always honoured. episode_attachment_window computes the bounded, date-based fallback
window used when a caller opts in to admitting same-person facts that weren't explicitly
linked.
Resolution diagnostics. Episode_EventView.resolved_event already resolves an
Episode_Event link best-effort, returning None on failure. ResolvedEpisodeEvent
extends it to explain why — a miscoded field concept, a target class not yet
registered, or a genuinely dangling reference:
from omop_alchemy.toolkit.episodes.handling import ResolvedEpisodeEvent
ee = session.get(ResolvedEpisodeEvent, (episode_id, event_id, field_concept_id))
ee.resolved_event # the resolved row, or None
ee.event_resolution_diagnostics # [] if resolved cleanly, otherwise why not
Mix ResolvedEpisodeEventMixin into an episode view to reach diagnostics through
ordinary episode.episode_events traversal instead of a direct query — this is how
OncologyEpisode gets diagnostics on oncology-aware event resolution for free.
Retrieve and summarise the clinical facts an episode contains.
Once an episode exists, the recurring question is what belongs to it.
Some facts are linked explicitly through Episode_Event; others fall
inside the episode's dates but were never linked. This module resolves
both, and is explicit about which rule admitted each row.
Explicit Episode_Event links are always honoured. A bounded
date-based fallback can be enabled per caller for sources that do not
populate episode links reliably, and episode_attachment_window defines
the window used. Open-ended episodes are handled without letting the
window run unbounded.
DrugEpisodeMixin adds linked-drug retrieval to any episode view.
Subclasses supply the concept filter and grouping key::
class MyEpisode(DrugEpisodeMixin, EpisodeView):
_drug_concept_ids = my_concept_ids
episode.drug_exposures # resolved rows
episode.drug_exposure_summaries_by() # grouped summaries
Summaries group exposures by any key the caller chooses — drug concept,
ingredient, or regimen member — via DrugExposureSummary and
summarize_drug_exposures_by.
Dose quantities are frequently not comparable across agents, because
source units and quantities arrive unnormalised. DoseEvaluability
carries that judgement alongside the number, so a summary that cannot be
interpreted as a dose says so rather than presenting a misleading total.
An explicit Episode_Event link can still fail to resolve — a miscoded
field concept, a target class not yet registered, a dangling reference.
ResolvedEpisodeEvent reports which of those applied instead of
silently returning None; mix ResolvedEpisodeEventMixin into an
episode view to reach it through ordinary episode.episode_events
traversal.
DoseEvaluability
dataclass
¶
DoseEvaluability(
evaluable: bool, reason: Optional[str] = None
)
Whether a dose-like summary can be interpreted as a dose quantity.
Drug exposure rows often carry source units and quantities without enough normalization to compare across agents or regimens. Domain-specific dosing modules should use this as a small shared vocabulary instead of silently treating missing or mixed units as comparable.
DrugEpisodeMixin ¶
Episode mixin for generic linked-drug retrieval and summary.
Domain-specific subclasses can supply concept filters, grouping keys, and dose validity rules. This base layer only knows how to find and summarise drug exposure rows.
DrugExposureSummary
dataclass
¶
DrugExposureSummary(
group_key: object,
n_exposures: int,
first_start_date: Optional[date],
last_start_date: Optional[date],
total_quantity: Optional[float],
dose_unit_source_values: frozenset[str],
drug_concept_ids: frozenset[int],
)
Small generic summary of drug exposures grouped by a caller-chosen key.
The grouping key can be a drug concept, ingredient concept, regimen member, or any downstream classifier. This type deliberately does not encode SACT or any other clinical programme-specific policy.
from_exposures
classmethod
¶
from_exposures(
exposures: Sequence[Drug_Exposure], *, group_key: object
) -> Self
Summarize drug exposure rows for one grouping key.
EpisodeEventResolutionDiagnostic
dataclass
¶
EpisodeEventResolutionDiagnostic(
kind: ResolutionDiagnosticKind,
episode_id: int,
event_id: int,
episode_event_field_concept_id: int,
message: str,
)
Advisory detail for an episode_event row whose target cannot be resolved.
Resolution remains best-effort: unresolved rows still return None from
resolved_event. These diagnostics give maintenance and QA code enough
information to distinguish miscoded field concepts from harmless ORM
coverage gaps and genuine dangling event references.
ResolvedEpisodeEvent ¶
Bases: Episode_EventView
Episode-event view that explains why a link did not resolve.
Episode_EventView.resolved_event already resolves best-effort and
returns None on failure. event_resolution_diagnostics names which
of three reasons applied: the field concept is not a recognised
ModifierFieldConcepts value, it is recognised but no registered ORM
class declares it, or the target row itself is missing.
Query this class directly for diagnostics on a known episode_event, or
mix ResolvedEpisodeEventMixin into an episode view to reach it
through ordinary episode.episode_events traversal.
ResolvedEpisodeEventMixin ¶
Relationship override so episode views can traverse to resolution diagnostics.
Mix into an EpisodeContext-derived view to have episode_events
load ResolvedEpisodeEvent rows instead of the bare
Episode_EventView, making
episode.episode_events[0].event_resolution_diagnostics reachable
through ordinary traversal rather than a direct query.
episode_attachment_window ¶
episode_attachment_window(
episode,
*,
days_prior: int = DEFAULT_EPISODE_WINDOW_DAYS_PRIOR,
open_end_fallback_days: int = DEFAULT_EPISODE_OPEN_END_FALLBACK_DAYS,
) -> tuple[date, date]
Date window used for episode-attributable facts admitted by date rather
than by an explicit Episode_Event link.
Time-varying facts such as body measurements use a bounded
episode-relative window. An explicit episode_end_date is always
honoured, however long the episode ran. Only open-ended episodes fall back
to a finite post-start window, so a missing end date cannot absorb a
person's entire future history.
resolve_drug_exposure_series ¶
resolve_drug_exposure_series(
episode,
concept_ids: Optional[Sequence[int]] = None,
*,
include_window: bool = False,
) -> list[Drug_Exposure]
Resolve drug exposures linked to an episode.
Explicit Episode_Event links are always honoured. A bounded episode-date
fallback can be enabled by callers that have a meaningful concept set and
want to recover unlinked rows. The default stays explicit-only to avoid
accidentally attributing unrelated same-person medications to a drug
episode.