Skip to content

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.

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.

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 ModifierFieldConcepts value;
  • 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:

flowchart TD Root["Episode 1000 (root)
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.

event property
event: ClinicalEventIdentity

The event portion of this attachment identity.

from_event classmethod
from_event(
    event: ClinicalEventIdentity, *, episode_id: int
) -> EpisodeAttachmentIdentity

Add an episode to an already canonical cross-table event identity.

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.

has_side_preference property
has_side_preference: bool

Whether candidates are tiered by their side of the anchor first.

uses_absolute_distance property
uses_absolute_distance: bool

Whether ranking uses absolute distance after any side-preference tier.

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 explicit_first_ranked. It must be omitted for policies that do not rank fallback candidates.

None
window EpisodeWindowSpec

Episode-relative date window used to admit fallback candidates.

EpisodeWindowSpec()
include_diagnostics bool

If True, return an advisory diagnostic selectable as well as the attachment query. Building the queries does not execute them.

False

Returns:

Type Description
EpisodeAttachmentQueries

attachments preserves event columns and appends episode_id and attachment_method. diagnostics is None unless requested.

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.