Skip to content

climate_ref.results.values #

Typed, ORM-free surface for metric-value results.

Reader is the intent-named entry point that notebooks and (eventually) the API use, via its values sub-reader. ValuesReader's read methods return frozen, detached value objects wrapped in collections that offer to_pandas(). The objects are fully materialised, so they remain valid after the originating session closes. A notebook can build a DataFrame inside a with Database(...) block and keep using it afterwards.

Everything here is a thin layer over climate_ref.results._query (the shared Select builders) and climate_ref.results.outliers. power users who need the raw Select or ORM rows use those modules directly.

Facet #

Distinct values observed for one CV dimension across a filtered query.

Source code in packages/climate-ref/src/climate_ref/results/values.py
@attrs.frozen(kw_only=True)
class Facet:
    """Distinct values observed for one CV dimension across a filtered query."""

    key: str
    """The CV dimension name."""

    values: tuple[str, ...]
    """Distinct values observed for the dimension."""

key instance-attribute #

The CV dimension name.

values instance-attribute #

Distinct values observed for the dimension.

Reader #

Typed entry point to REF query results.

Constructed from a Database, which owns the session and the read-only story. This is a thin entry point: it exposes per-domain sub-readers as properties, values for metric-value reads, executions for execution-group reads, datasets for dataset reads, diagnostics for diagnostic reads, and artifacts for output path resolution (only available when a results root is supplied).

Source code in packages/climate-ref/src/climate_ref/results/values.py
class Reader:
    """
    Typed entry point to REF query results.

    Constructed from a [Database][climate_ref.database.Database], which owns the session and the
    read-only story. This is a thin entry point: it exposes per-domain sub-readers as properties,
    [values][climate_ref.results.values.Reader.values] for metric-value reads,
    [executions][climate_ref.results.values.Reader.executions] for execution-group reads,
    [datasets][climate_ref.results.values.Reader.datasets] for dataset reads,
    [diagnostics][climate_ref.results.values.Reader.diagnostics] for diagnostic reads, and
    [artifacts][climate_ref.results.values.Reader.artifacts] for output path resolution
    (only available when a ``results`` root is supplied).
    """

    def __init__(self, database: Database, results: Path | None = None) -> None:
        self._db = database
        self._results = results

    @property
    def session(self) -> Session:
        """The underlying database session."""
        return self._db.session

    @functools.cached_property
    def values(self) -> ValuesReader:
        """Metric-value reads (scalar/series/facets)."""
        return ValuesReader(self._db)

    @functools.cached_property
    def executions(self) -> ExecutionsReader:
        """Execution-group and execution reads."""
        return ExecutionsReader(self._db)

    @functools.cached_property
    def datasets(self) -> "DatasetsReader":
        """Dataset reads."""
        from climate_ref.results.datasets import DatasetsReader  # noqa: PLC0415

        return DatasetsReader(self._db)

    @functools.cached_property
    def diagnostics(self) -> "DiagnosticsReader":
        """Diagnostic reads."""
        from climate_ref.results.diagnostics import DiagnosticsReader  # noqa: PLC0415

        return DiagnosticsReader(self._db)

    @functools.cached_property
    def artifacts(self) -> "ArtifactsReader":
        """
        Output path resolution.

        Raises ``ValueError`` when no ``results`` root was supplied to the constructor.
        """
        if self._results is None:
            raise ValueError(
                "reader.artifacts requires a results root; construct "
                "Reader(database, results=config.paths.results)."
            )
        from climate_ref.results.artifacts import ArtifactsReader  # noqa: PLC0415

        return ArtifactsReader(self._results)

artifacts cached property #

Output path resolution.

Raises ValueError when no results root was supplied to the constructor.

datasets cached property #

Dataset reads.

diagnostics cached property #

Diagnostic reads.

executions cached property #

Execution-group and execution reads.

session property #

The underlying database session.

values cached property #

Metric-value reads (scalar/series/facets).

ScalarValue #

A single scalar metric value, detached from the ORM.

Source code in packages/climate-ref/src/climate_ref/results/values.py
@attrs.frozen(kw_only=True)
class ScalarValue:
    """A single scalar metric value, detached from the ORM."""

    id: int
    """Primary key of the underlying ``ScalarMetricValue`` row."""

    execution_id: int
    """The execution that produced this value."""

    execution_group_id: int
    """The execution group the producing execution belongs to."""

    value: float | None
    """The scalar value itself."""

    kind: str
    """Model/reference kind, resolved from the ``kind`` CV dimension (defaults to ``"model"``)."""

    dimensions: Mapping[str, str]
    """
    CV dimension values (e.g. ``source_id``, ``statistic``, ``metric``) for this row.

    ``kind`` is excluded here and surfaced as the dedicated ``kind`` field instead.
    """

    attributes: Mapping[str, Any]
    """Free-form, non-CV attributes attached to the row."""

    is_outlier: bool | None = None
    """Per-row outlier verdict; ``None`` unless outlier detection ran."""

    verification_status: str | None = None
    """``"verified"``/``"unverified"`` status paired with ``is_outlier``; ``None`` unless detection ran."""

    diagnostic_slug: str | None = None
    """Owning diagnostic's slug, populated only when ``include_context=True``."""

    provider_slug: str | None = None
    """Owning provider's slug, populated only when ``include_context=True``."""

attributes instance-attribute #

Free-form, non-CV attributes attached to the row.

diagnostic_slug = None class-attribute instance-attribute #

Owning diagnostic's slug, populated only when include_context=True.

dimensions instance-attribute #

CV dimension values (e.g. source_id, statistic, metric) for this row.

kind is excluded here and surfaced as the dedicated kind field instead.

execution_group_id instance-attribute #

The execution group the producing execution belongs to.

execution_id instance-attribute #

The execution that produced this value.

id instance-attribute #

Primary key of the underlying ScalarMetricValue row.

is_outlier = None class-attribute instance-attribute #

Per-row outlier verdict; None unless outlier detection ran.

kind instance-attribute #

Model/reference kind, resolved from the kind CV dimension (defaults to "model").

provider_slug = None class-attribute instance-attribute #

Owning provider's slug, populated only when include_context=True.

value instance-attribute #

The scalar value itself.

verification_status = None class-attribute instance-attribute #

"verified"/"unverified" status paired with is_outlier; None unless detection ran.

ScalarValueCollection #

An immutable page of scalar values plus collection-level, outlier-aware metadata.

Source code in packages/climate-ref/src/climate_ref/results/values.py
@attrs.frozen(kw_only=True)
class ScalarValueCollection:
    """An immutable page of scalar values plus collection-level, outlier-aware metadata."""

    items: tuple[ScalarValue, ...]
    """The scalar values on this page."""

    total_count: int
    """Total values matching the filter before ``offset``/``limit`` (after outlier removal, if any)."""

    facets: tuple[Facet, ...]
    """Distinct CV dimension values observed over the full filtered set, before pagination."""

    offset: int
    """Rows skipped before this page."""

    limit: int | None
    """Page size requested, or ``None`` when the whole result was returned."""

    had_outliers: bool
    """Whether outlier detection ran and flagged at least one value."""

    outlier_count: int
    """Number of values flagged as outliers, or ``0`` when detection did not run."""

    def __iter__(self) -> Iterator[ScalarValue]:
        return iter(self.items)

    def __len__(self) -> int:
        return len(self.items)

    def facets_dict(self) -> dict[str, list[str]]:
        """Facets as a plain ``{dimension: [values]}`` mapping."""
        return {f.key: list(f.values) for f in self.facets}

    def to_pandas(self) -> pd.DataFrame:
        """Tidy DataFrame: one row per value, one column per CV dimension present, plus metadata."""
        detection_ran = any(v.is_outlier is not None for v in self.items)
        return scalar_values_to_frame(self.items, detection_ran=detection_ran)

facets instance-attribute #

Distinct CV dimension values observed over the full filtered set, before pagination.

had_outliers instance-attribute #

Whether outlier detection ran and flagged at least one value.

items instance-attribute #

The scalar values on this page.

limit instance-attribute #

Page size requested, or None when the whole result was returned.

offset instance-attribute #

Rows skipped before this page.

outlier_count instance-attribute #

Number of values flagged as outliers, or 0 when detection did not run.

total_count instance-attribute #

Total values matching the filter before offset/limit (after outlier removal, if any).

facets_dict() #

Facets as a plain {dimension: [values]} mapping.

Source code in packages/climate-ref/src/climate_ref/results/values.py
def facets_dict(self) -> dict[str, list[str]]:
    """Facets as a plain ``{dimension: [values]}`` mapping."""
    return {f.key: list(f.values) for f in self.facets}

to_pandas() #

Tidy DataFrame: one row per value, one column per CV dimension present, plus metadata.

Source code in packages/climate-ref/src/climate_ref/results/values.py
def to_pandas(self) -> pd.DataFrame:
    """Tidy DataFrame: one row per value, one column per CV dimension present, plus metadata."""
    detection_ran = any(v.is_outlier is not None for v in self.items)
    return scalar_values_to_frame(self.items, detection_ran=detection_ran)

SeriesValue #

A 1-d series metric value, detached from the ORM. The index is snapshotted from the shared axis.

Source code in packages/climate-ref/src/climate_ref/results/values.py
@attrs.frozen(kw_only=True)
class SeriesValue:
    """A 1-d series metric value, detached from the ORM. The index is snapshotted from the shared axis."""

    id: int
    """Primary key of the underlying ``SeriesMetricValue`` row."""

    execution_id: int
    """The execution that produced this value."""

    execution_group_id: int
    """The execution group the producing execution belongs to."""

    values: Sequence[float | int]
    """The series' y-values, in index order."""

    index: Sequence[float | int | str] | None
    """The shared index axis values, snapshotted at read time."""

    index_name: str | None
    """Name of the shared index axis (e.g. a time or latitude label)."""

    reference_id: str | None
    """Set for observation/reference series; ``None`` for model series."""

    kind: str
    """Model/reference kind, resolved from the ``kind`` CV dimension (defaults to ``"model"``)."""

    dimensions: Mapping[str, str]
    """
    CV dimension values (e.g. ``source_id``, ``statistic``, ``metric``) for this row.

    ``kind`` is excluded here and surfaced as the dedicated ``kind`` field instead.
    """

    attributes: Mapping[str, Any]
    """Free-form, non-CV attributes attached to the row."""

    diagnostic_slug: str | None = None
    """Owning diagnostic's slug, populated only when ``include_context=True``."""

    provider_slug: str | None = None
    """Owning provider's slug, populated only when ``include_context=True``."""

    @property
    def is_reference(self) -> bool:
        """True for observation/reference series (``reference_id`` is set)."""
        return self.reference_id is not None

attributes instance-attribute #

Free-form, non-CV attributes attached to the row.

diagnostic_slug = None class-attribute instance-attribute #

Owning diagnostic's slug, populated only when include_context=True.

dimensions instance-attribute #

CV dimension values (e.g. source_id, statistic, metric) for this row.

kind is excluded here and surfaced as the dedicated kind field instead.

execution_group_id instance-attribute #

The execution group the producing execution belongs to.

execution_id instance-attribute #

The execution that produced this value.

id instance-attribute #

Primary key of the underlying SeriesMetricValue row.

index instance-attribute #

The shared index axis values, snapshotted at read time.

index_name instance-attribute #

Name of the shared index axis (e.g. a time or latitude label).

is_reference property #

True for observation/reference series (reference_id is set).

kind instance-attribute #

Model/reference kind, resolved from the kind CV dimension (defaults to "model").

provider_slug = None class-attribute instance-attribute #

Owning provider's slug, populated only when include_context=True.

reference_id instance-attribute #

Set for observation/reference series; None for model series.

values instance-attribute #

The series' y-values, in index order.

SeriesValueCollection #

An immutable page of series values plus collection-level metadata.

Source code in packages/climate-ref/src/climate_ref/results/values.py
@attrs.frozen(kw_only=True)
class SeriesValueCollection:
    """An immutable page of series values plus collection-level metadata."""

    items: tuple[SeriesValue, ...]
    """The series values on this page."""

    total_count: int
    """Total values matching the filter before ``offset``/``limit``."""

    facets: tuple[Facet, ...]
    """Distinct CV dimension values observed over the full filtered set, before pagination."""

    offset: int
    """Rows skipped before this page."""

    limit: int | None
    """Page size requested, or ``None`` when the whole result was returned."""

    def __iter__(self) -> Iterator[SeriesValue]:
        return iter(self.items)

    def __len__(self) -> int:
        return len(self.items)

    def facets_dict(self) -> dict[str, list[str]]:
        """Facets as a plain ``{dimension: [values]}`` mapping."""
        return {f.key: list(f.values) for f in self.facets}

    def to_pandas(self, *, explode: bool = True) -> pd.DataFrame:
        """
        DataFrame of the series values.

        With ``explode=True`` (default) the result is long-form: one row per (series, index point),
        matching the API's CSV shape. With ``explode=False`` each series is one row with list-valued
        ``values``/``index`` cells.
        """
        return series_values_to_frame(self.items, explode=explode)

facets instance-attribute #

Distinct CV dimension values observed over the full filtered set, before pagination.

items instance-attribute #

The series values on this page.

limit instance-attribute #

Page size requested, or None when the whole result was returned.

offset instance-attribute #

Rows skipped before this page.

total_count instance-attribute #

Total values matching the filter before offset/limit.

facets_dict() #

Facets as a plain {dimension: [values]} mapping.

Source code in packages/climate-ref/src/climate_ref/results/values.py
def facets_dict(self) -> dict[str, list[str]]:
    """Facets as a plain ``{dimension: [values]}`` mapping."""
    return {f.key: list(f.values) for f in self.facets}

to_pandas(*, explode=True) #

DataFrame of the series values.

With explode=True (default) the result is long-form: one row per (series, index point), matching the API's CSV shape. With explode=False each series is one row with list-valued values/index cells.

Source code in packages/climate-ref/src/climate_ref/results/values.py
def to_pandas(self, *, explode: bool = True) -> pd.DataFrame:
    """
    DataFrame of the series values.

    With ``explode=True`` (default) the result is long-form: one row per (series, index point),
    matching the API's CSV shape. With ``explode=False`` each series is one row with list-valued
    ``values``/``index`` cells.
    """
    return series_values_to_frame(self.items, explode=explode)

ValuesReader #

Metric-value read domain: scalar/series values and their facets.

Constructed from a Database, which owns the session and the read-only story. All read methods return detached collections that outlive the session.

Source code in packages/climate-ref/src/climate_ref/results/values.py
class ValuesReader:
    """
    Metric-value read domain: scalar/series values and their facets.

    Constructed from a [Database][climate_ref.database.Database], which owns the session and the
    read-only story. All read methods return detached collections that outlive the session.
    """

    def __init__(self, database: Database) -> None:
        self._db = database

    @property
    def session(self) -> Session:
        """The underlying database session."""
        return self._db.session

    def _facets(self, base_stmt: Any, entity: Any) -> tuple[Facet, ...]:
        facet_map = collect_facets(self.session, base_stmt, entity)
        return tuple(Facet(key=k, values=tuple(v)) for k, v in facet_map.items())

    def scalar_values(  # noqa: PLR0913
        self,
        filters: MetricValueFilter | None = None,
        *,
        outliers: OutlierPolicy | None = None,
        include_unverified: bool = False,
        offset: int = 0,
        limit: int | None = None,
        with_facets: bool = True,
        include_context: bool = False,
    ) -> ScalarValueCollection:
        """
        Query scalar values, returning an outlier-aware collection.

        When outlier detection is disabled (the default) pagination and counting happen in SQL,
        so cost scales with the requested page rather than the whole result set. When ``outliers``
        is enabled, detection runs over the FULL filtered set so IQR bounds are globally
        consistent; with ``include_unverified`` False, flagged values are then removed before
        pagination and excluded from ``total_count``. ``facets`` are always computed over the full
        filtered set (before any outlier removal and pagination).
        """
        filters = filters or MetricValueFilter()
        policy = outliers or OutlierPolicy(method="off")

        base_stmt = select_scalar_values(filters)
        facets = self._facets(base_stmt, ScalarMetricValue) if with_facets else ()

        if not policy.enabled:
            # No detection: page and count in SQL so we never materialise the whole table.
            total_count = count_values(self.session, base_stmt)
            load_stmt = base_stmt.options(self._scalar_loader(include_context))
            if limit is not None:
                load_stmt = load_stmt.offset(offset).limit(limit)
            elif offset:
                load_stmt = load_stmt.offset(offset)
            rows = list(self.session.execute(load_stmt).scalars().all())
            items = tuple(
                self._to_scalar_dto(r, False, "verified", include_context, detection_ran=False) for r in rows
            )
            return ScalarValueCollection(
                items=items,
                total_count=total_count,
                facets=facets,
                offset=offset,
                limit=limit,
                had_outliers=False,
                outlier_count=0,
            )

        # Detection enabled: materialise the full filtered set so IQR bounds are globally
        # consistent, then filter and paginate in Python.
        load_stmt = base_stmt.options(self._scalar_loader(include_context))
        rows = list(self.session.execute(load_stmt).scalars().all())

        annotated, outlier_count = detect_scalar_outliers(rows, policy)
        had_outliers = outlier_count > 0
        if not include_unverified:
            annotated = [a for a in annotated if not a.is_outlier]

        total_count = len(annotated)
        page = annotated[offset : offset + limit] if limit is not None else annotated[offset:]
        items = tuple(
            self._to_scalar_dto(
                a.value, a.is_outlier, a.verification_status, include_context, detection_ran=True
            )
            for a in page
        )
        return ScalarValueCollection(
            items=items,
            total_count=total_count,
            facets=facets,
            offset=offset,
            limit=limit,
            had_outliers=had_outliers,
            outlier_count=outlier_count,
        )

    def series_values(
        self,
        filters: MetricValueFilter | None = None,
        *,
        offset: int = 0,
        limit: int | None = None,
        with_facets: bool = True,
        include_context: bool = False,
    ) -> SeriesValueCollection:
        """
        Query series values with SQL-level pagination.

        The shared index axis is resolved so ``index``/``index_name`` are populated. ``facets`` are
        computed over the full filtered set before pagination.
        """
        filters = filters or MetricValueFilter()

        base_stmt = select_series_values(filters)
        facets = self._facets(base_stmt, SeriesMetricValue) if with_facets else ()
        total_count = count_values(self.session, base_stmt)

        load_stmt = base_stmt.options(self._series_loader(include_context))
        if limit is not None:
            load_stmt = load_stmt.offset(offset).limit(limit)
        elif offset:
            load_stmt = load_stmt.offset(offset)
        rows = list(self.session.execute(load_stmt).scalars().unique().all())

        items = tuple(self._to_series_dto(r, include_context) for r in rows)
        return SeriesValueCollection(
            items=items, total_count=total_count, facets=facets, offset=offset, limit=limit
        )

    # -- loaders / materialisers ------------------------------------------------
    @staticmethod
    def _scalar_loader(include_context: bool) -> Any:
        exec_load = joinedload(ScalarMetricValue.execution)
        if include_context:
            return (
                exec_load.joinedload(Execution.execution_group)
                .joinedload(ExecutionGroup.diagnostic)
                .joinedload(Diagnostic.provider)
            )
        return exec_load

    @staticmethod
    def _series_loader(include_context: bool) -> Any:
        exec_load = joinedload(SeriesMetricValue.execution)
        if include_context:
            return (
                exec_load.joinedload(Execution.execution_group)
                .joinedload(ExecutionGroup.diagnostic)
                .joinedload(Diagnostic.provider)
            )
        return exec_load

    @staticmethod
    def _context_slugs(execution: Execution, include_context: bool) -> tuple[str | None, str | None]:
        if not include_context:
            return None, None
        diagnostic = execution.execution_group.diagnostic
        return diagnostic.slug, diagnostic.provider.slug

    def _to_scalar_dto(
        self,
        row: ScalarMetricValue,
        is_outlier: bool,
        verification_status: str,
        include_context: bool,
        detection_ran: bool,
    ) -> ScalarValue:
        diagnostic_slug, provider_slug = self._context_slugs(row.execution, include_context)
        dims = dict(row.dimensions)
        kind = _kind_of(dims)
        dims.pop("kind", None)
        return ScalarValue(
            id=row.id,
            execution_id=row.execution_id,
            execution_group_id=row.execution.execution_group_id,
            value=row.value,
            kind=kind,
            dimensions=dims,
            attributes=dict(row.attributes or {}),
            is_outlier=is_outlier if detection_ran else None,
            verification_status=verification_status if detection_ran else None,
            diagnostic_slug=diagnostic_slug,
            provider_slug=provider_slug,
        )

    def _to_series_dto(self, row: SeriesMetricValue, include_context: bool) -> SeriesValue:
        diagnostic_slug, provider_slug = self._context_slugs(row.execution, include_context)
        dims = dict(row.dimensions)
        kind = _kind_of(dims)
        dims.pop("kind", None)
        return SeriesValue(
            id=row.id,
            execution_id=row.execution_id,
            execution_group_id=row.execution.execution_group_id,
            values=list(row.values or []),
            index=list(row.index) if row.index is not None else None,
            index_name=row.index_name,
            reference_id=row.reference_id,
            kind=kind,
            dimensions=dims,
            attributes=dict(row.attributes or {}),
            diagnostic_slug=diagnostic_slug,
            provider_slug=provider_slug,
        )

session property #

The underlying database session.

scalar_values(filters=None, *, outliers=None, include_unverified=False, offset=0, limit=None, with_facets=True, include_context=False) #

Query scalar values, returning an outlier-aware collection.

When outlier detection is disabled (the default) pagination and counting happen in SQL, so cost scales with the requested page rather than the whole result set. When outliers is enabled, detection runs over the FULL filtered set so IQR bounds are globally consistent; with include_unverified False, flagged values are then removed before pagination and excluded from total_count. facets are always computed over the full filtered set (before any outlier removal and pagination).

Source code in packages/climate-ref/src/climate_ref/results/values.py
def scalar_values(  # noqa: PLR0913
    self,
    filters: MetricValueFilter | None = None,
    *,
    outliers: OutlierPolicy | None = None,
    include_unverified: bool = False,
    offset: int = 0,
    limit: int | None = None,
    with_facets: bool = True,
    include_context: bool = False,
) -> ScalarValueCollection:
    """
    Query scalar values, returning an outlier-aware collection.

    When outlier detection is disabled (the default) pagination and counting happen in SQL,
    so cost scales with the requested page rather than the whole result set. When ``outliers``
    is enabled, detection runs over the FULL filtered set so IQR bounds are globally
    consistent; with ``include_unverified`` False, flagged values are then removed before
    pagination and excluded from ``total_count``. ``facets`` are always computed over the full
    filtered set (before any outlier removal and pagination).
    """
    filters = filters or MetricValueFilter()
    policy = outliers or OutlierPolicy(method="off")

    base_stmt = select_scalar_values(filters)
    facets = self._facets(base_stmt, ScalarMetricValue) if with_facets else ()

    if not policy.enabled:
        # No detection: page and count in SQL so we never materialise the whole table.
        total_count = count_values(self.session, base_stmt)
        load_stmt = base_stmt.options(self._scalar_loader(include_context))
        if limit is not None:
            load_stmt = load_stmt.offset(offset).limit(limit)
        elif offset:
            load_stmt = load_stmt.offset(offset)
        rows = list(self.session.execute(load_stmt).scalars().all())
        items = tuple(
            self._to_scalar_dto(r, False, "verified", include_context, detection_ran=False) for r in rows
        )
        return ScalarValueCollection(
            items=items,
            total_count=total_count,
            facets=facets,
            offset=offset,
            limit=limit,
            had_outliers=False,
            outlier_count=0,
        )

    # Detection enabled: materialise the full filtered set so IQR bounds are globally
    # consistent, then filter and paginate in Python.
    load_stmt = base_stmt.options(self._scalar_loader(include_context))
    rows = list(self.session.execute(load_stmt).scalars().all())

    annotated, outlier_count = detect_scalar_outliers(rows, policy)
    had_outliers = outlier_count > 0
    if not include_unverified:
        annotated = [a for a in annotated if not a.is_outlier]

    total_count = len(annotated)
    page = annotated[offset : offset + limit] if limit is not None else annotated[offset:]
    items = tuple(
        self._to_scalar_dto(
            a.value, a.is_outlier, a.verification_status, include_context, detection_ran=True
        )
        for a in page
    )
    return ScalarValueCollection(
        items=items,
        total_count=total_count,
        facets=facets,
        offset=offset,
        limit=limit,
        had_outliers=had_outliers,
        outlier_count=outlier_count,
    )

series_values(filters=None, *, offset=0, limit=None, with_facets=True, include_context=False) #

Query series values with SQL-level pagination.

The shared index axis is resolved so index/index_name are populated. facets are computed over the full filtered set before pagination.

Source code in packages/climate-ref/src/climate_ref/results/values.py
def series_values(
    self,
    filters: MetricValueFilter | None = None,
    *,
    offset: int = 0,
    limit: int | None = None,
    with_facets: bool = True,
    include_context: bool = False,
) -> SeriesValueCollection:
    """
    Query series values with SQL-level pagination.

    The shared index axis is resolved so ``index``/``index_name`` are populated. ``facets`` are
    computed over the full filtered set before pagination.
    """
    filters = filters or MetricValueFilter()

    base_stmt = select_series_values(filters)
    facets = self._facets(base_stmt, SeriesMetricValue) if with_facets else ()
    total_count = count_values(self.session, base_stmt)

    load_stmt = base_stmt.options(self._series_loader(include_context))
    if limit is not None:
        load_stmt = load_stmt.offset(offset).limit(limit)
    elif offset:
        load_stmt = load_stmt.offset(offset)
    rows = list(self.session.execute(load_stmt).scalars().unique().all())

    items = tuple(self._to_series_dto(r, include_context) for r in rows)
    return SeriesValueCollection(
        items=items, total_count=total_count, facets=facets, offset=offset, limit=limit
    )