rrf-trace.test.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import { describe, expect, test } from "vitest";
  2. import { buildRrfTrace, reciprocalRankFusion, type RankedResult } from "../src/store";
  3. describe("buildRrfTrace", () => {
  4. test("matches reciprocalRankFusion totals and records per-list contributions", () => {
  5. const list1: RankedResult[] = [
  6. { file: "qmd://docs/a.md", displayPath: "docs/a.md", title: "A", body: "", score: 0.92 },
  7. { file: "qmd://docs/b.md", displayPath: "docs/b.md", title: "B", body: "", score: 0.81 },
  8. ];
  9. const list2: RankedResult[] = [
  10. { file: "qmd://docs/b.md", displayPath: "docs/b.md", title: "B", body: "", score: 0.77 },
  11. { file: "qmd://docs/a.md", displayPath: "docs/a.md", title: "A", body: "", score: 0.65 },
  12. ];
  13. const weights = [2.0, 1.0];
  14. const traces = buildRrfTrace(
  15. [list1, list2],
  16. weights,
  17. [
  18. { source: "fts", queryType: "lex", query: "lex query" },
  19. { source: "vec", queryType: "vec", query: "vec query" },
  20. ]
  21. );
  22. const fused = reciprocalRankFusion([list1, list2], weights);
  23. for (const result of fused) {
  24. const trace = traces.get(result.file);
  25. expect(trace).toBeDefined();
  26. expect(trace!.totalScore).toBeCloseTo(result.score, 10);
  27. }
  28. const aTrace = traces.get("qmd://docs/a.md")!;
  29. expect(aTrace.contributions).toHaveLength(2);
  30. expect(aTrace.contributions[0]?.source).toBe("fts");
  31. expect(aTrace.contributions[1]?.source).toBe("vec");
  32. expect(aTrace.topRank).toBe(1);
  33. expect(aTrace.topRankBonus).toBeCloseTo(0.05, 10);
  34. });
  35. test("applies top-rank bonus thresholds correctly", () => {
  36. const list: RankedResult[] = [
  37. { file: "qmd://docs/r1.md", displayPath: "docs/r1.md", title: "R1", body: "", score: 0.9 },
  38. { file: "qmd://docs/r2.md", displayPath: "docs/r2.md", title: "R2", body: "", score: 0.8 },
  39. { file: "qmd://docs/r3.md", displayPath: "docs/r3.md", title: "R3", body: "", score: 0.7 },
  40. { file: "qmd://docs/r4.md", displayPath: "docs/r4.md", title: "R4", body: "", score: 0.6 },
  41. ];
  42. const traces = buildRrfTrace([list], [1.0], [{ source: "fts", queryType: "lex", query: "rank" }]);
  43. expect(traces.get("qmd://docs/r1.md")?.topRankBonus).toBeCloseTo(0.05, 10);
  44. expect(traces.get("qmd://docs/r2.md")?.topRankBonus).toBeCloseTo(0.02, 10);
  45. expect(traces.get("qmd://docs/r3.md")?.topRankBonus).toBeCloseTo(0.02, 10);
  46. expect(traces.get("qmd://docs/r4.md")?.topRankBonus).toBeCloseTo(0.0, 10);
  47. });
  48. });