Implement Reciprocal Rank Fusion (RRF) that merges two ranked lists from different scoring distributions into a single ranked list. No parameter tuning required.
## Why RRF, not weighted linear
pgvector returns cosine similarity in `[0.0, 1.0]`. OpenSearch BM25 returns unbounded scores in `[0, 50+]`. These distributions are incomparable.
**Weighted linear (0.6 * sem + 0.4 * lex)** requires min-max normalisation, which is fragile: one outlier score compresses all other scores to near-zero. It also requires choosing weights, which requires labelled data we don't have yet.
**RRF** ignores score magnitudes entirely. It uses only **rank positions**: the document that appears first in a list gets rank 1, second gets rank 2, etc. The formula is:
```
RRF_score(d) = Σ 1 / (k + rank_i(d))
lists
```
Where `k = 60` is a constant (academic standard, Cormack et al. 2009). A document in rank 1 of both lists gets `1/61 + 1/61 = 0.0328`. A document in rank 1 of only one list gets `1/61 = 0.0164`. The first always outranks the second, regardless of original score magnitudes.
## Design
```rust
pubstructRRFConfig{
pubk: f32,// 60.0 (constant, don't tune)
pubretrieve_k: usize,// 50 (top-K from each engine)
pubfinal_k: usize,// 10 (return top-K)
}
pubstructRRFFusion{config: RRFConfig}
implRRFFusion{
pubfnfuse(
&self,
semantic: Vec<(String,f32)>,// (chunk_id, score) — sorted by score desc
1.`fuse([(a,0.9),(b,0.8)], [(a,8.0),(c,7.0)])` → `a` is rank 1 (appears in both lists).
2.`fuse([(a,0.9)], [])` → `a` is rank 1 with score `1/61`.
3.`fuse([], [])` → empty result.
4.`fuse([(a,0.9),(b,0.8)], [(b,8.0),(a,7.0)])` → `a` and `b` have equal RRF scores (both appear in both at same combined rank sum). Either order is acceptable.
5. Output length never exceeds `final_k`.
## Verify
```bash
cargo test -p mem-cli rrf -- --nocapture
```
**False pass:** Fusion returns results but sorted by original score, not RRF score. Verify by checking that a document ranked #3 in semantic but #1 in lexical outranks a document ranked #1 in semantic but absent from lexical.