"""Global rank computation for the leaderboard tabs. Ranks are computed once at startup over the full model list, before any filtering or partitioning into Open-Weight / Proprietary tables. They are properties of the model on this benchmark version and do not change when users apply filters or re-sort interactively. Tie handling: standard competition ranking ("1224"). scores 70, 65, 65, 60 → ranks 1, 2, 2, 4 Module-level export ------------------- GLOBAL_RANKS : dict[str, dict[str, int]] Keyed by the short pillar names from config.PILLARS: 'overall', 'spatial', 'st', 'temporal', 'semantic'. Populated the first time compute_global_ranks() is called at startup. Import directly:: from util.ranking import GLOBAL_RANKS """ from __future__ import annotations from typing import Iterable from .config import HEADLINE_FIELD_BY_TAB from .data import ModelRecord # Map short pillar key (config.PILLARS) → score field in ModelRecord.scores. # 'st' is the short form of 'spatio_temporal' used by the new UI layer. _PILLAR_SCORE_FIELD: dict[str, str] = { "overall": "overall", "spatial": "spatial", "st": "spatio_temporal", "temporal": "temporal", "semantic": "semantic", } # Module-level export. Starts empty; populated by compute_global_ranks() # on the first (and only) call at app startup. GLOBAL_RANKS: dict[str, dict[str, int]] = {} # -- Core ranking primitive ------------------------------------------------ def _rank_models(models: list[ModelRecord], score_field: str) -> dict[str, int]: """Return ``{model_id: rank}`` using 1-2-2-4 competition ranking. Sort key: descending score, ascending name as deterministic tiebreaker. The score field must be present in every model's scores dict. """ sorted_models = sorted( models, key=lambda m: (-_headline(m, score_field), m.name), ) ranks: dict[str, int] = {} prev_score: float | None = None prev_rank: int = 0 for position, m in enumerate(sorted_models, start=1): s = m.scores[score_field] if prev_score is not None and s == prev_score: ranks[m.id] = prev_rank else: ranks[m.id] = position prev_rank = position prev_score = s return ranks # -- Public API ------------------------------------------------------------ def compute_global_ranks( models: list[ModelRecord], tabs: Iterable[str] ) -> dict[str, dict[str, int]]: """Return ``{tab: {model_id: rank}}`` with global standard-competition ranks. Sort key per tab: descending by the tab's headline score, ascending by model name as deterministic tiebreaker. Required score fields are guaranteed present by upstream validation. Side-effect: populates the module-level ``GLOBAL_RANKS`` dict using the short pillar keys from ``config.PILLARS`` ('overall', 'spatial', 'st', 'temporal', 'semantic'). This happens once at app startup so any module can ``from util.ranking import GLOBAL_RANKS`` after startup. """ # Build return value keyed by caller-supplied tab names (backward-compat). result: dict[str, dict[str, int]] = {} for tab in tabs: field = HEADLINE_FIELD_BY_TAB[tab] result[tab] = _rank_models(models, field) # Populate GLOBAL_RANKS with short pillar keys for the new UI layer. for pillar_key, score_field in _PILLAR_SCORE_FIELD.items(): GLOBAL_RANKS[pillar_key] = _rank_models(models, score_field) return result def sort_model_ids_by_rank( model_ids: Iterable[str], rank_map: dict[str, int] ) -> list[str]: """Return ``model_ids`` sorted by ascending rank. Ids missing from ``rank_map`` are placed last in their input order. """ sentinel = float("inf") return sorted(model_ids, key=lambda mid: rank_map.get(mid, sentinel)) def _headline(m: ModelRecord, field: str) -> float: # Required scores guaranteed by validation; defensive default keeps # this function total in case it is called pre-validation. v = m.scores.get(field) return v if v is not None else float("-inf")