"""Evaluate ordered results with identity-bound graded relevance evidence. The executable fixture is illustrative. Unjudged candidates contribute zero gain but reduce coverage, so missing labels cannot silently certify a release. """ from __future__ import annotations from dataclasses import asdict, dataclass import hashlib import json import math import re from typing import Any, Iterable MAX_QUERIES = 5_000 MAX_CANDIDATES_PER_QUERY = 1_000 MAX_RELEVANCE_GRADE = 10 MAX_EXACT_GRADE_GAIN = 1_000_000 MIN_QUERY_WEIGHT_RATIO = 1e-12 _IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,127}$") def _identifier(name: str, value: object) -> str: if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value): raise ValueError(f"{name} must be a stable identifier") return value def _owner(name: str, value: object) -> str: checked = _identifier(name, value) if not checked.startswith("team:"): raise ValueError(f"{name} must identify an accountable team owner") return checked def _finite(name: str, value: object) -> float: if isinstance(value, bool) or not isinstance(value, (int, float)): raise ValueError(f"{name} must be a real number") checked = float(value) if not math.isfinite(checked): raise ValueError(f"{name} must be finite") return checked def _probability(name: str, value: object) -> float: checked = _finite(name, value) if checked < 0.0 or checked > 1.0: raise ValueError(f"{name} must be between zero and one") return checked def _positive_int(name: str, value: object, maximum: int) -> int: if isinstance(value, bool) or not isinstance(value, int) or value <= 0: raise ValueError(f"{name} must be a positive integer") if value > maximum: raise ValueError(f"{name} exceeds {maximum}") return value def _grade(value: object) -> int: if type(value) is not int or value < 0 or value > MAX_RELEVANCE_GRADE: raise ValueError( f"relevance_grade must be an integer from 0 to {MAX_RELEVANCE_GRADE}" ) return value def _canonical(value: Any) -> Any: if isinstance(value, dict): return {key: _canonical(item) for key, item in sorted(value.items())} if isinstance(value, tuple): return [_canonical(item) for item in value] return value def _content_id(prefix: str, payload: dict[str, Any]) -> str: encoded = json.dumps( _canonical(payload), sort_keys=True, separators=(",", ":"), allow_nan=False ).encode("utf-8") return f"{prefix}@sha256:{hashlib.sha256(encoded).hexdigest()}" @dataclass(frozen=True) class RelevanceJudgment: judgment_id: str query_id: str candidate_id: str relevance_grade: int cohort_id: str evaluation_id: str evaluation_window: str label_version: str judgment_policy_version: str def __post_init__(self) -> None: for field in ( "judgment_id", "query_id", "candidate_id", "cohort_id", "evaluation_id", "evaluation_window", "label_version", "judgment_policy_version", ): _identifier(field, getattr(self, field)) _grade(self.relevance_grade) @dataclass(frozen=True) class QueryRanking: ranking_id: str query_id: str candidate_ids: tuple[str, ...] judgments: tuple[RelevanceJudgment, ...] query_weight: float cohort_id: str evaluation_id: str evaluation_window: str model_version: str candidate_set_version: str index_version: str feature_version: str def __post_init__(self) -> None: for field in ( "ranking_id", "query_id", "cohort_id", "evaluation_id", "evaluation_window", "model_version", "candidate_set_version", "index_version", "feature_version", ): _identifier(field, getattr(self, field)) if not isinstance(self.candidate_ids, tuple) or not self.candidate_ids: raise TypeError("candidate_ids must be a non-empty immutable tuple") if len(self.candidate_ids) > MAX_CANDIDATES_PER_QUERY: raise ValueError("candidate_ids exceed the global safety limit") for candidate_id in self.candidate_ids: _identifier("candidate_id", candidate_id) if len(self.candidate_ids) != len(set(self.candidate_ids)): raise ValueError("candidate_ids must be unique and ordered") if not isinstance(self.judgments, tuple): raise TypeError("judgments must be an immutable tuple") if any(type(item) is not RelevanceJudgment for item in self.judgments): raise TypeError("judgments must contain concrete RelevanceJudgment values") weight = _finite("query_weight", self.query_weight) if weight <= 0.0: raise ValueError("query_weight must be positive") @dataclass(frozen=True) class RankingContract: contract_version: str evaluation_id: str evaluation_window: str cohort_id: str model_version: str candidate_set_version: str index_version: str feature_version: str label_version: str judgment_policy_version: str utility_table_version: str maximum_relevance_grade: int grade_gains: tuple[int, ...] maximum_grade_gain: int numeric_precision_convention: str metric_name: str gain_convention: str discount_convention: str aggregation_convention: str unjudged_convention: str cutoff: int minimum_queries: int maximum_queries: int maximum_candidates_per_query: int minimum_judgment_coverage: float minimum_mean_ndcg: float minimum_query_judgment_coverage: float minimum_query_ndcg: float minimum_query_weight_ratio: float ranking_owner: str candidate_owner: str label_owner: str metric_owner: str decision_owner: str def __post_init__(self) -> None: for field in ( "contract_version", "evaluation_id", "evaluation_window", "cohort_id", "model_version", "candidate_set_version", "index_version", "feature_version", "label_version", "judgment_policy_version", "utility_table_version", ): _identifier(field, getattr(self, field)) for field in ( "ranking_owner", "candidate_owner", "label_owner", "metric_owner", "decision_owner", ): _owner(field, getattr(self, field)) if self.metric_name != "ndcg-at-k": raise ValueError("metric_name must be ndcg-at-k") if self.gain_convention != "contracted-grade-utility-table": raise ValueError("unsupported gain_convention") if ( self.numeric_precision_convention != "binary64-safe-integer-gain-and-scale-normalized-weight-v1" ): raise ValueError("unsupported numeric_precision_convention") if self.discount_convention != "log2-rank-plus-one": raise ValueError("unsupported discount_convention") if self.aggregation_convention != "query-weighted-arithmetic-mean": raise ValueError("unsupported aggregation_convention") if self.unjudged_convention != "zero-gain-and-count-against-coverage": raise ValueError("unsupported unjudged_convention") _positive_int( "maximum_relevance_grade", self.maximum_relevance_grade, MAX_RELEVANCE_GRADE, ) if not isinstance(self.grade_gains, tuple) or len(self.grade_gains) != ( self.maximum_relevance_grade + 1 ): raise TypeError( "grade_gains must be an immutable tuple aligned to every grade" ) if self.grade_gains[0] != 0: raise ValueError("grade_gains must assign zero utility to grade zero") maximum_grade_gain = _positive_int( "maximum_grade_gain", self.maximum_grade_gain, MAX_EXACT_GRADE_GAIN ) previous = -1 for gain in self.grade_gains: if isinstance(gain, bool) or not isinstance(gain, int) or gain < 0: raise ValueError("grade_gains must contain non-negative integers") if gain <= previous: raise ValueError("grade_gains must be strictly increasing") if gain > maximum_grade_gain: raise ValueError("grade_gains exceed maximum_grade_gain") previous = gain _positive_int("cutoff", self.cutoff, MAX_CANDIDATES_PER_QUERY) _positive_int("minimum_queries", self.minimum_queries, MAX_QUERIES) _positive_int("maximum_queries", self.maximum_queries, MAX_QUERIES) _positive_int( "maximum_candidates_per_query", self.maximum_candidates_per_query, MAX_CANDIDATES_PER_QUERY, ) if self.minimum_queries > self.maximum_queries: raise ValueError("minimum_queries exceeds maximum_queries") if self.cutoff > self.maximum_candidates_per_query: raise ValueError("cutoff exceeds maximum_candidates_per_query") _probability("minimum_judgment_coverage", self.minimum_judgment_coverage) _probability("minimum_mean_ndcg", self.minimum_mean_ndcg) _probability( "minimum_query_judgment_coverage", self.minimum_query_judgment_coverage, ) _probability("minimum_query_ndcg", self.minimum_query_ndcg) weight_ratio = _probability( "minimum_query_weight_ratio", self.minimum_query_weight_ratio ) if weight_ratio < MIN_QUERY_WEIGHT_RATIO: raise ValueError( f"minimum_query_weight_ratio must be at least {MIN_QUERY_WEIGHT_RATIO}" ) @property def content_id(self) -> str: return _content_id("ranking-contract", asdict(self)) @dataclass(frozen=True) class QueryMetric: query_id: str ranking_content_id: str ndcg: float judgment_coverage: float query_weight: float @dataclass(frozen=True) class RankingReport: report_id: str contract_content_id: str evidence_id: str query_count: int mean_ndcg: float judgment_coverage: float query_metrics: tuple[QueryMetric, ...] decision: str def __post_init__(self) -> None: for field in ("report_id", "contract_content_id", "evidence_id"): _identifier(field, getattr(self, field)) _positive_int("query_count", self.query_count, MAX_QUERIES) _probability("mean_ndcg", self.mean_ndcg) _probability("judgment_coverage", self.judgment_coverage) if not isinstance(self.query_metrics, tuple) or len(self.query_metrics) != self.query_count: raise TypeError("query_metrics must be an aligned immutable tuple") if any(type(item) is not QueryMetric for item in self.query_metrics): raise TypeError("query_metrics must contain concrete QueryMetric values") if self.decision not in ("PASS", "HOLD"): raise ValueError("decision must be PASS or HOLD") def _ranking_content_id(ranking: QueryRanking) -> str: return _content_id("query-ranking", asdict(ranking)) def _dcg(grades: tuple[int, ...], grade_gains: tuple[int, ...]) -> float: value = math.fsum( grade_gains[grade] / math.log2(rank + 1) for rank, grade in enumerate(grades, start=1) ) if not math.isfinite(value): raise OverflowError("DCG overflowed") return value def evaluate_rankings( contract: RankingContract, *, evidence_id: str, rankings: Iterable[QueryRanking], ) -> RankingReport: if type(contract) is not RankingContract: raise TypeError("contract must be a concrete RankingContract") contract.__post_init__() checked_evidence_id = _identifier("evidence_id", evidence_id) frozen = tuple(rankings) if len(frozen) < contract.minimum_queries: raise ValueError("ranking evidence is below minimum_queries") if len(frozen) > contract.maximum_queries: raise ValueError("ranking evidence exceeds maximum_queries") if any(type(item) is not QueryRanking for item in frozen): raise TypeError("rankings must contain concrete QueryRanking values") ranking_ids = tuple(item.ranking_id for item in frozen) query_ids = tuple(item.query_id for item in frozen) if len(ranking_ids) != len(set(ranking_ids)): raise ValueError("duplicate ranking_id") if len(query_ids) != len(set(query_ids)): raise ValueError("duplicate query_id") expected_scope = ( contract.cohort_id, contract.evaluation_id, contract.evaluation_window, contract.model_version, contract.candidate_set_version, contract.index_version, contract.feature_version, ) metrics: list[QueryMetric] = [] all_judgment_ids: list[str] = [] total_judged = 0 total_slots = 0 for ranking in frozen: ranking.__post_init__() if len(ranking.candidate_ids) < contract.cutoff: raise ValueError("ranking is shorter than the contracted cutoff") if len(ranking.candidate_ids) > contract.maximum_candidates_per_query: raise ValueError("ranking exceeds maximum_candidates_per_query") observed_scope = ( ranking.cohort_id, ranking.evaluation_id, ranking.evaluation_window, ranking.model_version, ranking.candidate_set_version, ranking.index_version, ranking.feature_version, ) if observed_scope != expected_scope: raise ValueError("ranking scope does not match contract") judgment_ids: list[str] = [] judged_candidates: list[str] = [] grade_by_candidate: dict[str, int] = {} for judgment in ranking.judgments: judgment.__post_init__() judgment_ids.append(judgment.judgment_id) all_judgment_ids.append(judgment.judgment_id) judged_candidates.append(judgment.candidate_id) if judgment.query_id != ranking.query_id: raise ValueError("judgment query_id does not match ranking") judgment_scope = ( judgment.cohort_id, judgment.evaluation_id, judgment.evaluation_window, judgment.label_version, judgment.judgment_policy_version, ) expected_judgment_scope = ( contract.cohort_id, contract.evaluation_id, contract.evaluation_window, contract.label_version, contract.judgment_policy_version, ) if judgment_scope != expected_judgment_scope: raise ValueError("judgment scope does not match contract") if judgment.candidate_id not in ranking.candidate_ids: raise ValueError("judgment references a candidate outside the ranking") if judgment.relevance_grade > contract.maximum_relevance_grade: raise ValueError("judgment grade exceeds contracted maximum") grade_by_candidate[judgment.candidate_id] = judgment.relevance_grade if len(judgment_ids) != len(set(judgment_ids)): raise ValueError("duplicate judgment_id") if len(judged_candidates) != len(set(judged_candidates)): raise ValueError("duplicate candidate judgment") top_candidates = ranking.candidate_ids[: contract.cutoff] top_grades = tuple(grade_by_candidate.get(item, 0) for item in top_candidates) judged_in_top = sum(item in grade_by_candidate for item in top_candidates) if not any(grade > 0 for grade in grade_by_candidate.values()): raise ValueError("query has no positive relevance judgment") ideal_grades = tuple( sorted(grade_by_candidate.values(), reverse=True)[: contract.cutoff] ) ideal_grades += (0,) * (contract.cutoff - len(ideal_grades)) ideal_dcg = _dcg(ideal_grades, contract.grade_gains) if ideal_dcg <= 0.0: raise ValueError("query has no positive ideal gain") ndcg = _dcg(top_grades, contract.grade_gains) / ideal_dcg coverage = judged_in_top / contract.cutoff if not math.isfinite(ndcg) or not math.isfinite(coverage): raise OverflowError("ranking metric overflowed") metrics.append( QueryMetric( query_id=ranking.query_id, ranking_content_id=_ranking_content_id(ranking), ndcg=ndcg, judgment_coverage=coverage, query_weight=ranking.query_weight, ) ) total_judged += judged_in_top total_slots += contract.cutoff if len(all_judgment_ids) != len(set(all_judgment_ids)): raise ValueError("duplicate judgment_id across queries") maximum_weight = max(item.query_weight for item in metrics) minimum_weight_ratio = min(item.query_weight for item in metrics) / maximum_weight if minimum_weight_ratio < contract.minimum_query_weight_ratio: raise ValueError("query weight ratio is below the contracted minimum") scaled_weights = tuple(item.query_weight / maximum_weight for item in metrics) total_scaled_weight = math.fsum(scaled_weights) mean_ndcg = math.fsum( item.ndcg * weight for item, weight in zip(metrics, scaled_weights) ) / total_scaled_weight coverage = total_judged / total_slots decision = ( "PASS" if mean_ndcg >= contract.minimum_mean_ndcg and coverage >= contract.minimum_judgment_coverage and all(item.ndcg >= contract.minimum_query_ndcg for item in metrics) and all( item.judgment_coverage >= contract.minimum_query_judgment_coverage for item in metrics ) else "HOLD" ) report_payload = { "contract_content_id": contract.content_id, "evidence_id": checked_evidence_id, "query_count": len(frozen), "mean_ndcg": mean_ndcg, "judgment_coverage": coverage, "query_metrics": tuple(metrics), "decision": decision, } return RankingReport( report_id=_content_id( "ranking-report", { **report_payload, "query_metrics": tuple(asdict(item) for item in metrics), }, ), **report_payload, ) def _example() -> None: contract = RankingContract( contract_version="ranking-evaluation-v1", evaluation_id="search-ranking-eval-2026-08", evaluation_window="2026-08-01/2026-08-14", cohort_id="english-support-search", model_version="ranker-v6", candidate_set_version="support-candidates-v4", index_version="support-index-2026-08-14", feature_version="ranking-features-v3", label_version="graded-relevance-v2", judgment_policy_version="support-rubric-v2", utility_table_version="support-search-utility-v1", maximum_relevance_grade=3, grade_gains=(0, 1, 3, 7), maximum_grade_gain=1_000, numeric_precision_convention="binary64-safe-integer-gain-and-scale-normalized-weight-v1", metric_name="ndcg-at-k", gain_convention="contracted-grade-utility-table", discount_convention="log2-rank-plus-one", aggregation_convention="query-weighted-arithmetic-mean", unjudged_convention="zero-gain-and-count-against-coverage", cutoff=4, minimum_queries=2, maximum_queries=100, maximum_candidates_per_query=20, minimum_judgment_coverage=1.0, minimum_mean_ndcg=0.90, minimum_query_judgment_coverage=1.0, minimum_query_ndcg=0.80, minimum_query_weight_ratio=1e-12, ranking_owner="team:search-ranking", candidate_owner="team:search-retrieval", label_owner="team:support-quality", metric_owner="team:search-measurement", decision_owner="team:support-search", ) def ranking(index: int, candidates: tuple[str, ...], grades: tuple[int, ...]) -> QueryRanking: if len(candidates) != len(grades): raise ValueError("example candidates and grades must align") query_id = f"query-{index}" judgments = tuple( RelevanceJudgment( judgment_id=f"judgment-{index}-{position}", query_id=query_id, candidate_id=candidate_id, relevance_grade=grade, cohort_id=contract.cohort_id, evaluation_id=contract.evaluation_id, evaluation_window=contract.evaluation_window, label_version=contract.label_version, judgment_policy_version=contract.judgment_policy_version, ) for position, (candidate_id, grade) in enumerate( zip(candidates, grades), start=1 ) ) return QueryRanking( ranking_id=f"ranking-{index}", query_id=query_id, candidate_ids=candidates, judgments=judgments, query_weight=1.0, cohort_id=contract.cohort_id, evaluation_id=contract.evaluation_id, evaluation_window=contract.evaluation_window, model_version=contract.model_version, candidate_set_version=contract.candidate_set_version, index_version=contract.index_version, feature_version=contract.feature_version, ) rankings = ( ranking(1, ("doc-a", "doc-b", "doc-c", "doc-d"), (3, 2, 1, 0)), ranking(2, ("doc-e", "doc-f", "doc-g", "doc-h"), (2, 3, 1, 0)), ) report = evaluate_rankings( contract, evidence_id="ranking-batch-001", rankings=rankings ) print("example=illustrative_only") print(f"contract_version={contract.contract_version}") print(f"metric={contract.metric_name}") print(f"cutoff={contract.cutoff}") print(f"queries={report.query_count}") print(f"mean_ndcg={report.mean_ndcg:.3f}") print(f"judgment_coverage={report.judgment_coverage:.3f}") print(f"decision={report.decision}") if __name__ == "__main__": _example()