import { describe, expect, it } from "vitest"; import { RRF_K, fuseHybrid, reciprocalRankFusion } from "./rrf.js"; describe("reciprocalRankFusion", () => { it("uses the standard constant k=60", () => { expect(RRF_K).toBe(60); }); it("scores a doc in both lists higher than a doc in one", () => { const fused = reciprocalRankFusion([ { name: "bm25", docIds: ["a", "b"] }, { name: "vec", docIds: ["a", "c"] }, ]); // a: 1/61 + 1/61 ; b: bm25 rank 2 → 1/62 ; c: vec rank 2 → 1/62 (tie → docId) expect(fused[0]!.docId).toBe("a"); expect(fused[0]!.score).toBeCloseTo(2 / 61, 12); expect(fused[0]!.sources).toEqual(["bm25", "vec"]); expect(fused[1]!.score).toBeCloseTo(1 / 62, 12); expect(fused[2]!.score).toBeCloseTo(1 / 62, 12); }); it("rank matters: 2nd place contributes 1/(k+2)", () => { const fused = reciprocalRankFusion([{ name: "x", docIds: ["a", "b", "c"] }]); expect(fused[0]!.docId).toBe("a"); expect(fused[0]!.score).toBeCloseTo(1 / 61, 12); expect(fused[1]!.score).toBeCloseTo(1 / 62, 12); expect(fused[2]!.score).toBeCloseTo(1 / 63, 12); }); it("a doc ranked 1 in one list beats a doc ranked 1+1 in both only when the sum is higher", () => { // x: 1/61 + 1/62 ≈ 0.03252 ; y: 1/61 const fused = reciprocalRankFusion([ { name: "l1", docIds: ["x", "y"] }, { name: "l2", docIds: ["x"] }, ]); expect(fused[0]!.docId).toBe("x"); expect(fused[1]!.docId).toBe("y"); }); it("breaks score ties deterministically by docId", () => { const fused = reciprocalRankFusion([ { name: "a", docIds: ["z"] }, { name: "b", docIds: ["m"] }, ]); expect(fused.map((f) => f.docId)).toEqual(["m", "z"]); }); it("respects limit", () => { const fused = reciprocalRankFusion( [{ name: "x", docIds: ["a", "b", "c", "d"] }], 60, 2, ); expect(fused).toHaveLength(2); }); it("handles empty lists", () => { expect(reciprocalRankFusion([{ name: "x", docIds: [] }])).toEqual([]); }); it("throws on non-positive k", () => { expect(() => reciprocalRankFusion([], 0)).toThrow(); }); it("fuseHybrid names the sources bm25/vec", () => { const fused = fuseHybrid(["a", "b"], ["b", "c"]); const b = fused.find((f) => f.docId === "b"); expect(b?.sources).toEqual(["bm25", "vec"]); expect(fused[0]!.docId).toBe("b"); }); });