Reciprocal rank fusion merges two ranked lists — say, BM25 results and vector-search results over your chargeback cases — into one. It has exactly one parameter, k. And the answer is just... 60. Not tuned per dataset. Not learned. Sixty, from the original paper, works essentially everywhere.
The entire algorithm:
def rrf(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
"""Each ranking is a list of doc IDs, best first. Score = sum of 1/(k + rank)."""
scores = {}







