Episodes¶
Episode APIs answer two related questions: how episodes relate to one another, and which clinical facts belong to an episode. They do not assume a specialty. Oncology-specific episode types compose these APIs with governed oncology concepts in the analytics package.
Retrieve facts from an episode¶
For a treatment episode, a common first task is to retrieve its linked drug exposures and group them by drug concept:
from omop_alchemy.cdm.model.structural import EpisodeView
from omop_alchemy.toolkit.episodes.handling import DrugEpisodeMixin
class TreatmentEpisode(DrugEpisodeMixin, EpisodeView):
_drug_concept_ids = treatment_drug_concept_ids
episode = session.get(TreatmentEpisode, episode_id)
if episode is None:
raise LookupError(f"Unknown episode: {episode_id}")
exposures = episode.drug_exposures
summaries = episode.drug_exposure_summaries_by()
drug_exposures uses explicit Episode_Event links by default. _drug_concept_ids limits the rows to the concepts meaningful for this episode type, and drug_exposure_summaries_by() groups the selected rows by drug_concept_id. Pass a key function when another grouping, such as ingredient or regimen member, is more useful.
If rows have already been selected elsewhere, construct or group summaries directly:
from omop_alchemy.toolkit.episodes.handling import (
DrugExposureSummary,
summarize_drug_exposures_by,
)
regimen_summary = DrugExposureSummary.from_exposures(
regimen_exposures,
group_key="regimen-a",
)
by_drug = summarize_drug_exposures_by(
regimen_exposures,
key=lambda exposure: exposure.drug_concept_id,
)
The generic summary reports counts, dates, source units, concepts, and a raw quantity total. A total is only clinically comparable when the source quantities have compatible meaning and units. Domain-specific summaries can attach DoseEvaluability to make that judgement explicit, as the oncology SACT and radiotherapy summaries do.
Explicit links and date windows¶
An Episode_Event row is the strongest statement that a fact belongs to an episode, so linked facts are always retained. Some datasets do not populate these links consistently. A caller can opt into bounded date-window retrieval for drug exposures by setting _include_window_drug_exposures = True on its mixin class.
Window retrieval must be paired with a meaningful concept filter. Without one, every same-person drug exposure inside the dates is eligible. The generic helper deliberately defaults to explicit links only.
episode_attachment_window() provides a related bounded window for episode-attributable facts. Its lower bound is a configurable number of days before the episode start. Its upper bound is the recorded episode end, or a finite fallback after the start when the episode is open-ended. The finite fallback prevents an incomplete episode from absorbing the rest of a person's record.
Understand unresolved episode links¶
Episode_EventView.resolved_event returns the linked analytical ORM row when the field concept and target row can be resolved, otherwise None. Measurement, Observation, and Device Exposure links resolve to MeasurementView, ObservationView, and Device_ExposureView; their bare table mappings remain available for lightweight ETL. The target map uses the same stable CDM metadata as canonical event projections, so importing domain-specific analytics cannot change the default target. ResolvedEpisodeEvent preserves the resolution behaviour and adds a diagnostic that distinguishes three cases:
- the field concept is not a recognised
ModifierFieldConceptsvalue; - the field concept is recognised but no ORM target class is registered for it; or
- the target row does not exist.
from omop_alchemy.toolkit.episodes.handling import ResolvedEpisodeEvent
link = session.get(
ResolvedEpisodeEvent,
(episode_id, event_id, field_concept_id),
)
if link is not None and link.resolved_event is None:
for diagnostic in link.event_resolution_diagnostics:
logger.warning("%s: %s", diagnostic.kind, diagnostic.message)
Use ResolvedEpisodeEventMixin on an episode view when diagnostics should be available through episode.episode_events rather than through a separate query.
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.
Traverse an episode hierarchy¶
canonical_episode_projection() selects the stable structural fields without joining vocabulary tables. Pass include_concept_label=True when a result intended for inspection or presentation also needs episode_concept_name. The vocabulary lookup is optional: an episode remains in the result with a null label when its concept is zero, absent, or unavailable in a subset vocabulary.
from omop_alchemy.toolkit.episodes.derivation import canonical_episode_projection
episodes = canonical_episode_projection(include_concept_label=True)
For a parent episode, episode_descendants() returns a recursive CTE containing the root at depth zero and each descendant at its distance from that root:
from sqlalchemy import select
from omop_alchemy.toolkit.episodes.derivation import episode_descendants
hierarchy = episode_descendants(root_episode_id=episode_id)
statement = select(
hierarchy.c.episode_id,
hierarchy.c.episode_parent_id,
hierarchy.c.depth,
).order_by(hierarchy.c.depth, hierarchy.c.episode_id)
rows = session.execute(statement).mappings().all()
Traversal follows parent IDs only within the same person and stops at a configurable maximum depth, which bounds malformed cyclic data. Set include_root=False when only descendants are needed. direct_episode_relationship_projection() provides a non-recursive parent-child result for callers that need one level only.
episode_event_hierarchy_projection() joins the hierarchy to Episode_Event and retains the root episode, the episode that owns the link, and its depth. This lets a caller include child-linked evidence without encoding a specialty-specific number of child levels:
depth 0"] --> C1["Episode 1001
depth 1"] Root --> C2["Episode 1002
depth 1"] C1 --> GC1["Episode 1010
depth 2"] C2 --> GC2["Episode 1011
depth 2"] Ev(["Procedure_Occurrence 7
Episode_Event link"]) -. "joined via
episode_event_hierarchy_projection()" .-> GC1
The link at depth 2 is visible to code operating on the root at depth 0, without the caller having to know how many levels separate them.
Describe episode attachment policy¶
The derivation package provides declarative types for code that assigns events to episodes. The types keep four choices visible: whether explicit links take precedence, whether fallback may return one or several episodes, which side of an anchor date is preferred, and how candidates within that preference are ranked.
For example, the following policy honours a valid explicit link and otherwise chooses one episode. Episodes that had started by the event date are considered before future episodes, and the nearest start date wins within that group:
from omop_alchemy.toolkit.episodes.derivation import (
EpisodeAttachmentPolicy,
EpisodeWindowSpec,
TemporalRankingSpec,
TemporalSelectionPolicy,
TemporalSidePreference,
)
attachment = EpisodeAttachmentPolicy.explicit_first_ranked
window = EpisodeWindowSpec(
days_prior=90,
open_end_fallback_days=365,
)
ranking = TemporalRankingSpec(
policy=TemporalSelectionPolicy.nearest,
stable_id_column="episode_id",
side_preference=TemporalSidePreference.on_or_before_anchor,
)
Pass the policy, window, and—only for ranked fallback—ranking to episode_attachment_queries() with a canonical event projection. The builder validates explicit links by event ID, Field-concept discriminator, episode ID, and person; suppresses fallback only after a valid link; and returns deterministic attachments plus optional diagnostics. All-in-window fallback accepts the window but rejects a ranking because it deliberately retains every admitted episode. episode_window_predicate(), temporal_order_expressions(), and temporal_row_number() remain available when a query needs the individual portable SQL pieces. Date arithmetic is implemented for PostgreSQL and SQLite; compiling it for another dialect fails rather than assuming PostgreSQL syntax.
See Query contracts for the complete result shape, attachment example, boundaries, repeated-observation selection, and the distinction between absolute-nearest and already-started-first ranking.
Construct episodes and resolve the relationships between them.
Episodes in the CDM are rows that reference each other through
episode_parent_id and resolve clinical facts through Episode_Event.
The public contracts in this area define episode-attachment identities and
policies used by query builders. Shared clinical-event row names and identities
live in toolkit.core.events.
Projection, attachment, and ranking helpers return SQLAlchemy statements or expressions without executing them.
CANONICAL_ATTACHMENT_DIAGNOSTIC_COLUMNS
module-attribute
¶
CANONICAL_ATTACHMENT_DIAGNOSTIC_COLUMNS: tuple[
AttachmentDiagnosticColumn, ...
] = tuple(AttachmentDiagnosticColumn)
Columns exposed by an attachment diagnostics query.
CANONICAL_EPISODE_COLUMNS
module-attribute
¶
CANONICAL_EPISODE_COLUMNS: tuple[EpisodeColumn, ...] = (
tuple(
column
for column in EpisodeColumn
if column is not episode_concept_name
)
)
Columns exposed by the canonical episode projection.
CANONICAL_EPISODE_OPTIONAL_COLUMNS
module-attribute
¶
CANONICAL_EPISODE_OPTIONAL_COLUMNS: tuple[
EpisodeColumn, ...
] = (episode_concept_name,)
Opt-in descriptive columns exposed by an enriched episode projection.
AttachmentDiagnosticCode ¶
Bases: StrEnum
Stable categories for explaining rejected or ambiguous attachment rows.
AttachmentDiagnosticColumn ¶
Bases: StrEnum
Stable labels emitted by an attachment diagnostics query.
EpisodeAttachmentDiagnostic
dataclass
¶
EpisodeAttachmentDiagnostic(
code: AttachmentDiagnosticCode,
event: ClinicalEventIdentity,
event_field_concept_id: int,
message: str,
linked_event_field_concept_id: int | None = None,
episode_id: int | None = None,
candidate_count: int | None = None,
)
Typed advisory result returned by an attachment diagnostics query.
from_mapping
classmethod
¶
from_mapping(
row: _EpisodeAttachmentDiagnosticMapping,
) -> "EpisodeAttachmentDiagnostic"
Convert one SQLAlchemy mapping result without leaking column-name handling.
EpisodeAttachmentIdentity
dataclass
¶
EpisodeAttachmentIdentity(
event_source_table: str, event_id: int, episode_id: int
)
Unique identity of one event attached to one episode.
EpisodeAttachmentMethod ¶
Bases: StrEnum
How an event-to-episode attachment was established.
EpisodeAttachmentPolicy ¶
Bases: StrEnum
Named precedence and fallback-cardinality policies.
A valid explicit link always wins in the two explicit-first policies. The difference is what happens to an event that has no valid explicit link.
permits_fallback_fanout
property
¶
permits_fallback_fanout: bool
Whether one fallback event may attach to several episodes.
requires_fallback_ranking
property
¶
requires_fallback_ranking: bool
Whether fallback needs a separate temporal ranking specification.
uses_fallback
property
¶
uses_fallback: bool
Whether unlinked events may be attached by a date window.
EpisodeAttachmentQueries
dataclass
¶
EpisodeAttachmentQueries(
attachments: Select[Any],
diagnostics: Select[Any] | None = None,
)
Attachment results and, when requested, their advisory diagnostics.
attachments preserves the input event columns and appends episode_id
and attachment_method. Its executable uniqueness key is
(event_source_table, event_id, episode_id).
diagnostics is None unless requested. Diagnostics explain rejected
explicit links and fallback outcomes; they do not alter attachment rows.
EpisodeColumn ¶
Bases: StrEnum
Canonical labels emitted by an episode projection.
EpisodeWindowSpec
dataclass
¶
EpisodeWindowSpec(
days_prior: int = DEFAULT_EPISODE_WINDOW_DAYS_PRIOR,
open_end_fallback_days: int = DEFAULT_EPISODE_OPEN_END_FALLBACK_DAYS,
include_lower_bound: bool = True,
include_upper_bound: bool = True,
)
Finite episode-relative window used to admit fallback event candidates.
InvalidAttachmentSourceError ¶
Bases: ValueError
Raised when an attachment input does not expose its required columns.
ObservationSelectionPolicy ¶
Bases: StrEnum
Supported deterministic choices for repeated longitudinal observations.
ObservationSelectionSpec
dataclass
¶
ObservationSelectionSpec(
policy: ObservationSelectionPolicy,
partition_by: tuple[str, ...] = (
"person_id",
"observation_concept_id",
),
stable_id_column: str = "observation_id",
include_anchor_date: bool = True,
)
Partition and tie-break contract for repeated-observation selection.
The default partition describes one observation concept for one person. Callers may add an episode or another grouping field when their declared result grain requires it.
requires_anchor
property
¶
requires_anchor: bool
Whether selection requires a caller-supplied anchor date.
TemporalRankingSpec
dataclass
¶
TemporalRankingSpec(
policy: TemporalSelectionPolicy,
stable_id_column: str,
side_preference: TemporalSidePreference = TemporalSidePreference.none,
)
Temporal ranking contract for SQL builders.
A side preference, when present, is applied before the selection policy.
nearest then means the smallest absolute distance within that tier.
All policies use the named stable ID column as their final ascending
tie-breaker, so the same source rows cannot alternate across executions.
TemporalSelectionPolicy ¶
Bases: StrEnum
How one row is selected from several temporal candidates.
TemporalSidePreference ¶
Bases: StrEnum
Which side of an anchor is preferred before temporal ranking.
Candidate dates are ranked relative to a caller-supplied anchor. For event
attachment where the event is the anchor and episode starts are candidates,
on_or_before_anchor prefers an episode that had already started.
absolute_day_delta ¶
absolute_day_delta(
candidate_date: ColumnElement[Any],
anchor_date: ColumnElement[Any],
) -> sa.ColumnElement[int]
Return absolute calendar-day distance between candidate and anchor.
bounded_temporal_predicate ¶
bounded_temporal_predicate(
value: ColumnElement[Any],
lower_bound: ColumnElement[Any],
upper_bound: ColumnElement[Any],
*,
include_lower_bound: bool = True,
include_upper_bound: bool = True,
) -> sa.ColumnElement[bool]
Test a value against independently open or closed temporal bounds.
canonical_episode_projection ¶
canonical_episode_projection(
episode_model: EpisodeSource = Episode,
*,
include_concept_label: bool = False,
) -> sa.Select[Any]
Project stable episode fields, optionally including its concept name.
direct_episode_relationship_projection ¶
direct_episode_relationship_projection(
episode_model: EpisodeSource = Episode,
) -> sa.Select[Any]
Select direct parent-child pairs with a depth of one.
episode_attachment_queries ¶
episode_attachment_queries(
events: type[Any] | FromClause | SelectBase,
*,
policy: EpisodeAttachmentPolicy,
episodes: type[Episode]
| FromClause
| SelectBase = Episode,
episode_events: type[Episode_Event]
| FromClause
| SelectBase = Episode_Event,
ranking: TemporalRankingSpec | None = None,
window: EpisodeWindowSpec = EpisodeWindowSpec(),
include_diagnostics: bool = False,
) -> EpisodeAttachmentQueries
Build explicit-first attachments from canonical event and episode inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
type[Any] | FromClause | SelectBase
|
A supported event model or selectable exposing the canonical event columns. |
required |
policy
|
EpisodeAttachmentPolicy
|
Whether to use explicit links only, ranked fallback, or every eligible episode in the fallback window. |
required |
episodes
|
type[Episode] | FromClause | SelectBase
|
An Episode model or selectable exposing episode identity, person and date bounds. |
Episode
|
episode_events
|
type[Episode_Event] | FromClause | SelectBase
|
An Episode_Event model or selectable containing episode/event links and their Field-concept discriminator. |
Episode_Event
|
ranking
|
TemporalRankingSpec | None
|
Temporal ranking used by |
None
|
window
|
EpisodeWindowSpec
|
Episode-relative date window used to admit fallback candidates. |
EpisodeWindowSpec()
|
include_diagnostics
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
EpisodeAttachmentQueries
|
|
Notes
Resolution proceeds in three stages: validate explicit links, admit
same-person fallback candidates within the episode-relative window, then
retain every admitted candidate or apply temporal ranking according to
policy. Explicit links suppress fallback only after the event ID,
Field-concept discriminator, episode ID and person all agree; the final
result is deduplicated by event source, event ID and episode ID. Fallback
ambiguity counts distinct eligible episodes, even when inputs repeat rows.
Diagnostics explain rejected links and fallback outcomes without changing attachment rows. Because inputs may be filtered selectables, the builder does not infer missing source rows or unsupported discriminators from their absence.
episode_descendants ¶
episode_descendants(
*,
root_episode_id: int | ColumnElement[Any] | None = None,
episode_model: EpisodeSource = Episode,
include_root: bool = True,
max_depth: int = 100,
name: str = "episode_descendants",
) -> sa.CTE
Build a recursive root-to-descendant projection with bounded depth.
episode_event_hierarchy_projection ¶
episode_event_hierarchy_projection(
*,
root_episode_id: int | ColumnElement[Any] | None = None,
episode_model: EpisodeSource = Episode,
episode_event_model: EpisodeEventSource = Episode_Event,
include_root: bool = True,
max_depth: int = 100,
) -> sa.Select[Any]
Select linked events with their root episode, owning episode, and depth.
episode_window_bounds ¶
episode_window_bounds(
episode_start_date: ColumnElement[Any],
episode_end_date: ColumnElement[Any],
*,
window: EpisodeWindowSpec = EpisodeWindowSpec(),
) -> tuple[sa.ColumnElement[Any], sa.ColumnElement[Any]]
Build the bounded SQL interval used for date-admitted episode facts.
Closed episodes use their recorded end date. Open episodes use the configured fallback horizon so an absent end date cannot silently produce an unbounded join.
episode_window_predicate ¶
episode_window_predicate(
event_date: ColumnElement[Any],
episode_start_date: ColumnElement[Any],
episode_end_date: ColumnElement[Any],
*,
window: EpisodeWindowSpec = EpisodeWindowSpec(),
) -> sa.ColumnElement[bool]
Test whether an event lies inside a bounded episode-relative window.
observation_eligibility_predicate ¶
observation_eligibility_predicate(
observation_date: ColumnElement[Any],
spec: ObservationSelectionSpec,
*,
anchor_date: ColumnElement[Any] | None = None,
) -> sa.ColumnElement[bool]
Return the date predicate required by an observation selection policy.
observation_order_expressions ¶
observation_order_expressions(
observation_date: ColumnElement[Any],
stable_id: ColumnElement[Any],
spec: ObservationSelectionSpec,
) -> tuple[sa.ColumnElement[Any], ...]
Build date and stable-ID ordering for repeated observations.
observation_row_number ¶
observation_row_number(
columns: Any,
*,
observation_date_column: str,
spec: ObservationSelectionSpec,
label: str = "observation_rank",
) -> sa.ColumnElement[int]
Return a deterministic row number using the declared observation grain.
The caller supplies column names rather than mapped attributes so this helper works against raw OMOP sources and projected/aliased selects alike.
ranked_observation_select ¶
ranked_observation_select(
source: FromClause,
spec: ObservationSelectionSpec,
*,
observation_date_column: str = "observation_date",
anchor_date: ColumnElement[Any] | None = None,
rank_label: str = "observation_rank",
) -> sa.Select[Any]
Select source columns with deterministic rank and eligibility.
Eligibility is applied in the same select that computes the window rank, keeping out-of-window rows from competing for rank one. The source's existing columns are preserved and the rank is appended for downstream projection or filtering.
shift_date ¶
shift_date(
value: ColumnElement[Any], *, days: int
) -> sa.ColumnElement[Any]
Shift a date by a fixed number of days on PostgreSQL or SQLite.
signed_day_delta ¶
signed_day_delta(
candidate_date: ColumnElement[Any],
anchor_date: ColumnElement[Any],
) -> sa.ColumnElement[int]
Return candidate minus anchor in whole calendar days.
temporal_order_expressions ¶
temporal_order_expressions(
candidate_date: ColumnElement[Any],
anchor_date: ColumnElement[Any],
stable_id: ColumnElement[Any],
ranking: TemporalRankingSpec,
) -> tuple[sa.ColumnElement[Any], ...]
Build deterministic ordering for a temporal ranking contract.
temporal_row_number ¶
temporal_row_number(
candidate_date: ColumnElement[Any],
anchor_date: ColumnElement[Any],
stable_id: ColumnElement[Any],
ranking: TemporalRankingSpec,
*,
partition_by: Iterable[ColumnElement[Any]] = (),
label: str = "temporal_rank",
) -> sa.ColumnElement[int]
Return a deterministic row number for temporal candidates.