Spot the error in this hybrid search scoring code that combines BM25 and cosine scores.
candidates = bm25_top100 + dense_top100 for doc in candidates: doc.final = doc.bm25_score + doc.cosine_score return sorted(candidates, key=final)[:10]
# Reciprocal Rank Fusion: merge BM25 and dense rankings without score normalization.
# rank starts at 1; k=60 is the Cormack 2009 default and is robust across corpora.
def rrf(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
scores: dict[str, float] = {}
for ranked in rankings:
for rank, doc_id in enumerate(ranked, start=1):
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(scores.items(), key=lambda kv: -kv[1])
# Usage: dense and sparse arms each return ranked doc_ids.
fused = rrf([dense_results, bm25_results], k=60)
# WRONG: scores = [a + b for a, b in zip(bm25_scores, cosine_scores)]
# BM25 ~ [0, 40], cosine ~ [-1, 1]; the sum is effectively BM25 alone.