import pytest from rag.fusion import reciprocal_rank_fusion from rag.models import RetrievalDocument, SearchHit, SourceRef SOURCE = SourceRef(physical_page=1, precision="page") def _hit(doc_id: str, score: float = 1.0) -> SearchHit: return SearchHit( RetrievalDocument( doc_id=doc_id, drug_id="drug", kind="prose", text=doc_id, section_key="section", source_refs=(SOURCE,), ), score, ) def test_rrf_promotes_candidate_supported_by_dense_and_lexical_rankings() -> None: fused = reciprocal_rank_fusion( [[_hit("dense-only"), _hit("shared")], [_hit("shared"), _hit("lexical-only")]], rank_constant=60, ) assert [hit.document.doc_id for hit in fused] == [ "shared", "dense-only", "lexical-only", ] def test_rrf_deduplicates_a_document_within_one_ranking() -> None: fused = reciprocal_rank_fusion( [[_hit("duplicate"), _hit("duplicate")], [_hit("other")]], rank_constant=10, ) duplicate = next(hit for hit in fused if hit.document.doc_id == "duplicate") assert duplicate.score == pytest.approx(1 / 11) def test_rrf_limit_and_ties_are_deterministic() -> None: fused = reciprocal_rank_fusion( [[_hit("first")], [_hit("second")]], rank_constant=60, limit=1, ) assert [hit.document.doc_id for hit in fused] == ["first"] @pytest.mark.parametrize( ("kwargs", "message"), [({"rank_constant": 0}, "rank_constant"), ({"limit": -1}, "limit")], ) def test_rrf_rejects_invalid_configuration(kwargs, message: str) -> None: with pytest.raises(ValueError, match=message): reciprocal_rank_fusion([], **kwargs)