"""Deterministic reciprocal-rank fusion for repository evidence.""" from __future__ import annotations from dataclasses import replace from typing import Sequence from .components import Candidate def unique_files(candidates: Sequence[Candidate]) -> tuple[Candidate, ...]: seen: set[str] = set() result: list[Candidate] = [] for candidate in candidates: if candidate.path not in seen: seen.add(candidate.path) result.append(candidate) return tuple(result) def reciprocal_rank_fusion( rankings: Sequence[Sequence[Candidate]], limit: int, k: int = 60, ) -> tuple[Candidate, ...]: if len(rankings) < 2: raise ValueError("RRF requires at least two rankings") scores: dict[str, float] = {} representatives: dict[str, Candidate] = {} sources: dict[str, list[str]] = {} for ranking in rankings: for rank, candidate in enumerate(unique_files(tuple(ranking)), start=1): scores[candidate.path] = scores.get(candidate.path, 0.0) + 1.0 / (k + rank) sources.setdefault(candidate.path, []).append(candidate.source) if candidate.path not in representatives: representatives[candidate.path] = candidate ordered = sorted(scores, key=lambda path: (-scores[path], path))[:limit] return tuple( replace( representatives[path], source="rrf:" + "+".join(sorted(set(sources[path]))), score=scores[path], metadata={ **(representatives[path].metadata or {}), "fused_sources": tuple(sorted(set(sources[path]))), "rrf_k": k, }, ) for path in ordered )