from __future__ import annotations from collections import Counter import pytest from k1link.compute.e37_acceptance_contract import ( E37AcceptanceContractError, assign_split, derive_reference_labels, ) def _decision( *, detector: str, ownership: str, cause: str | None = None, effective_stratum: str | None = "agree", ) -> dict[str, object]: return { "detector_assessment": detector, "point_ownership": ownership, "cause_code": cause, "effective_stratum": effective_stratum, } def test_r0_reference_projection_keeps_dimensions_independent() -> None: assert derive_reference_labels( _decision(detector="valid", ownership="object"), None, ) == ("object-present", "object-associated", "current") assert derive_reference_labels( _decision( detector="false-positive", ownership="surface-or-background", cause="time_mismatch", ), None, ) == ("background-or-noise", "rejected-nonobject", "stale") assert derive_reference_labels( _decision( detector="not-applicable", ownership="insufficient-support", effective_stratum="camera-only", ), None, ) == ("unknown", "insufficient-support", "unavailable") def test_human_exception_only_overrides_the_ambiguous_reference() -> None: ambiguous = _decision( detector="insufficient-evidence", ownership="insufficient-evidence", effective_stratum=None, ) assert derive_reference_labels(ambiguous, "object-present") == ( "occupied-environment", "independent-occupied", "unavailable", ) assert derive_reference_labels(ambiguous, "background-or-noise") == ( "background-or-noise", "rejected-nonobject", "unavailable", ) def test_split_is_deterministic_balanced_and_complete() -> None: rows = [ { "item_id": f"item-{index:03d}", "source_stratum": "agree" if index % 2 else "conflict", "range_bucket": "near" if index % 3 else "far", } for index in range(60) ] first = assign_split( rows, seed="frozen-seed", validation_fraction=0.3, ) second = assign_split( list(reversed(rows)), seed="frozen-seed", validation_fraction=0.3, ) assert first == second assert set(first) == {row["item_id"] for row in rows} assert Counter(first.values())["validation"] == 18 def test_split_rejects_mutable_or_invalid_fraction() -> None: with pytest.raises(E37AcceptanceContractError): assign_split([], seed="", validation_fraction=0.3) with pytest.raises(E37AcceptanceContractError): assign_split([], seed="seed", validation_fraction=0.9)