"""
Frozen Pydantic BaseModels describing contact-matrix column contracts.
``CmFrameSchema`` is the canonical description of a contact-matrix
DataFrame: which column holds row IDs, which holds column IDs, which
holds counts, and (optionally) which holds raw counts. This replaces the
six ad-hoc column-name tuples currently scattered across loaders (e.g.
``DataFrameSpecs.ROW_IDS == "row_ids"``, etc.) with a single typed
contract that downstream code can validate against.
DOP alignment: Principle 4 (make illegal states unrepresentable). The
schema is ``frozen=True`` and ``extra="forbid"`` — a CmFrameSchema
instance cannot mutate after construction, and unknown columns cannot
slip in. If a loader returns a DataFrame whose columns don't match its
declared schema, the mismatch is caught at the loader boundary, not
silently propagated to downstream code.
Examples
--------
>>> from gunz_cm.structs.cm_schema import CmFrameSchema
>>> schema = CmFrameSchema(row_col="row_ids", col_col="col_ids", count_col="counts")
>>> schema.row_col
'row_ids'
>>> CmFrameSchema(row_col="row_ids", col_col="col_ids") # missing count_col
pydantic_core._pydantic_core.ValidationError: ...
Notes
-----
Phase 1 makes ``CmFrameSchema`` available but does NOT replace the
existing ``DataFrameSpecs`` constant yet. That swap happens in a later
phase once loaders have been audited for the column-set they actually
return. This phase establishes the *type*; downstream phases
(v2.20.0+) replace the ad-hoc string constants with this schema.
"""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field
[docs]class CmFrameSchema(BaseModel):
"""Canonical column contract for a contact-matrix DataFrame.
Parameters
----------
row_col : str
Column name holding the row bin index (0-based, local to the
loaded region).
col_col : str
Column name holding the column bin index (0-based, local to
the loaded region).
count_col : str
Column name holding the count (raw or balanced, depending on
the loader's ``balancing`` argument).
raw_count_col : str or None
Optional column name holding raw (unbalanced) counts. Present
only when the loader is invoked with ``return_raw_counts=True``.
Examples
--------
"""
model_config = ConfigDict(
frozen=True,
extra="forbid",
)
row_col: str = Field(
min_length=1,
max_length=64,
description="Column name for the row bin index.",
)
col_col: str = Field(
min_length=1,
max_length=64,
description="Column name for the column bin index.",
)
count_col: str = Field(
min_length=1,
max_length=64,
description="Column name for the (possibly balanced) count.",
)
raw_count_col: str | None = Field(
default=None,
description=(
"Optional column name for raw counts. Set by loaders invoked "
"with return_raw_counts=True; None for the normal case."
),
)
[docs] def has_raw_count(self) -> bool:
"""Return True if this schema carries a separate raw-count column.
Examples
--------
>>> CmFrameSchema(row_col="r", col_col="c", count_col="v").has_raw_count()
False
>>> CmFrameSchema(row_col="r", col_col="c", count_col="v", raw_count_col="raw_v").has_raw_count()
True
"""
return self.raw_count_col is not None
__all__ = ["CmFrameSchema"]