Source code for phenotypic.schema._tiers

"""Intermediate classification base classes for MeasurementInfo enums.

These member-less bases carry the coarse ``kind()`` and (for primary
measurements) ``tier()`` for the measurement-classification framework. A
measurement enum declares its classification by subclassing the matching
base instead of ``MeasurementInfo`` directly. Straddling enums subclass the
neutral parent (``PrimaryMeasure``/``DerivedMeasure``) and tag the minority
members with ``Entry(tier=...)`` / ``Entry(derivation_type=...)``.
"""

from __future__ import annotations

from functools import cache
from collections.abc import Iterable
from typing import Any, cast

from ._measurement_info import MeasurementInfo


class IdentityInfo(MeasurementInfo):
    """Identity / design-factor columns (metadata, locators)."""

    @classmethod
    def kind(cls) -> str:
        return "identity"


[docs] class MetadataInfo(IdentityInfo): """Identity information owned by a concrete metadata vocabulary. Every concrete owner emits the shared ``Metadata_<Label>`` namespace. Class identity, rather than a category-string prefix, is the stable signal that an enum owns metadata. """
[docs] @classmethod def category(cls) -> str: """Return the shared physical namespace for metadata columns.""" return "Metadata"
@classmethod def _missing_(cls, value: Any) -> MetadataInfo | None: """Resolve an exact previous-release header during enum deserialization. Enum pickles reconstruct members by calling their owner with the stored enum value. Previous releases therefore pass values such as ``MetadataGenetic_Strain`` to the renamed canonical owner. The permanent compatibility registry is imported lazily to keep schema import order acyclic while allowing those stored members to resolve by identity. Args: value: Enum value supplied by the deserializer. Returns: The matching member of ``cls``, or ``None`` for the normal Enum ``ValueError`` path. """ if not isinstance(value, str): return None from phenotypic.sdk_._metadata_compatibility import LEGACY_HEADER_TO_MEMBER member = LEGACY_HEADER_TO_MEMBER.get(value) return member if member is not None and type(member) is cls else None
[docs] @classmethod @cache def header_set(cls) -> frozenset[str]: """Return this owner's finite set of currently emitted headers.""" members = cast(Iterable[MetadataInfo], cls) return frozenset(member.value for member in members)
class QualityInfo(MeasurementInfo): """Quality / trust columns that gate analysis.""" @classmethod def kind(cls) -> str: return "quality" class DerivedMeasure(MeasurementInfo): """Model/derived outputs; per-member tier via Entry overrides.""" @classmethod def kind(cls) -> str: return "derived" class PrimaryMeasure(MeasurementInfo): """Primary measured signal with no fixed tier (used by straddlers).""" @classmethod def kind(cls) -> str: return "primary" class DirectPhenotype(PrimaryMeasure): """Tier 1 — semantic readout, safe to interpret a single value.""" @classmethod def tier(cls) -> int: return 1 class DescriptiveTrait(PrimaryMeasure): """Tier 2 — interpretable named trait; interpret directionally.""" @classmethod def tier(cls) -> int: return 2 class DiscriminativeFeature(PrimaryMeasure): """Tier 3 — agnostic fingerprint; use in aggregate for discrimination.""" @classmethod def tier(cls) -> int: return 3