diff --git a/Cargo.lock b/Cargo.lock index 939b9a9..d99625d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,7 @@ dependencies = [ "cueseg", "mdscrub", "pdftriage", + "rrf", "serde_json", "textkit", ] @@ -369,6 +370,15 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "rrf" +version = "0.1.0" +dependencies = [ + "clap", + "corpus-core", + "serde_json", +] + [[package]] name = "rustc-hash" version = "2.1.3" diff --git a/Cargo.toml b/Cargo.toml index 4ed9291..5d6ec45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ mdscrub = { path = "crates/mdscrub", version = "0.1.0" } pdftriage = { path = "crates/pdftriage", version = "0.1.0" } cuenorm = { path = "crates/cuenorm", version = "0.1.0" } cueseg = { path = "crates/cueseg", version = "0.1.0" } +rrf = { path = "crates/rrf", version = "0.1.0" } clap = { version = "4", features = ["derive"] } fancy-regex = "0.16" diff --git a/README.md b/README.md index 421de9f..c9f60b5 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ $ for f in *.pdf; do pdftriage --quiet "$f" && ocrmypdf "$f" "ocr/$f"; done | `pdftriage` | Whether a PDF's text layer is healthy, garbled, sparse, permuted, or absent — and so whether OCR is worth its cost | | `cuenorm` | Any subtitle format in, one normalised cue stream out | | `cueseg` | A timed transcript into topic chapters | +| `rrf` | Several ranked lists into one, without the database | ## The contract diff --git a/crates/rrf/Cargo.toml b/crates/rrf/Cargo.toml new file mode 100644 index 0000000..4187525 --- /dev/null +++ b/crates/rrf/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rrf" +description = "Reciprocal rank fusion: several ranked lists in, one ranked list out." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +clap.workspace = true +corpus-core.workspace = true +serde_json.workspace = true + +[lints] +workspace = true diff --git a/crates/rrf/src/lib.rs b/crates/rrf/src/lib.rs new file mode 100644 index 0000000..1c9dce1 --- /dev/null +++ b/crates/rrf/src/lib.rs @@ -0,0 +1,220 @@ +//! Reciprocal rank fusion: several ranked lists in, one ranked list out. +//! +//! The useful half of hybrid search, with none of the database. Vector search +//! and full-text search disagree about everything except what matters, and +//! their scores are not comparable — a cosine distance and a `ts_rank` are +//! different quantities on different scales. Fusion sidesteps that by +//! throwing the scores away and keeping only the ranks: a result that both +//! channels put near the top beats one that either channel loves alone. +//! +//! A channel may be weighted, which is how a metadata match outranks a +//! body-text tie: when somebody searches for a book by its title, the book +//! rarely repeats its own title in its body, so the channel that matched the +//! title is the one telling the truth. + +use std::collections::HashMap; + +use corpus_core::{Channel, Hit}; + +/// The constant that flattens the difference between rank 1 and rank 2. +/// +/// Sixty, from the paper the method comes from and from every implementation +/// since. It is large enough that the top of one list does not swamp the rest +/// of another, and it is the number every other system's fused scores are +/// comparable against, which is worth more than tuning it. +pub const K: f64 = 60.0; + +/// How much a metadata match counts for, against a body-text match at the +/// same rank. +pub const META_WEIGHT: f64 = 1.5; + +/// What each channel's rank is worth. +#[derive(Debug, Clone, PartialEq)] +pub struct Weights { + /// The rank constant. + pub k: f64, + /// Per-channel weights. + pub channels: HashMap, +} + +impl Default for Weights { + fn default() -> Self { + Self { + k: K, + channels: HashMap::from([ + (Channel::Vector, 1.0), + (Channel::Fts, 1.0), + (Channel::Meta, META_WEIGHT), + ]), + } + } +} + +impl Weights { + /// What one channel's rank is worth. An unnamed channel counts for one. + pub fn of(&self, channel: Channel) -> f64 { + self.channels.get(&channel).copied().unwrap_or(1.0) + } +} + +/// Fuse ranked lists into one. +/// +/// Every hit is grouped by its `id`, and each contributes `weight / (k + +/// rank)` — the rank the producer gave it, not its position in the stream, so +/// the lists may arrive interleaved. The record that comes back for an id is +/// the one that ranked it best, carrying its payload, with `rank` and `score` +/// replaced by its place and score in the fused list. +/// +/// Ties keep the order the ids were first seen in, so the same input always +/// produces the same output. +pub fn fuse(hits: &[Hit], weights: &Weights) -> Vec { + let mut order: Vec<&str> = Vec::new(); + let mut fused: HashMap<&str, Fusing<'_>> = HashMap::new(); + + for hit in hits { + let entry = fused.entry(hit.id.as_str()).or_insert_with(|| { + order.push(hit.id.as_str()); + Fusing { + best: hit, + score: 0.0, + seen: HashMap::new(), + } + }); + // A channel that ranks the same id twice is a producer contradicting + // itself; the later word is taken as the corrected one. + if let Some(previous) = entry.seen.insert(hit.channel, hit.rank) { + entry.score -= weights.of(hit.channel) / (weights.k + previous as f64); + } + entry.score += weights.of(hit.channel) / (weights.k + hit.rank as f64); + if hit.rank < entry.best.rank { + entry.best = hit; + } + } + + let mut ranked: Vec<(&str, f64)> = order + .iter() + .map(|id| (*id, fused[id].score)) + .collect::>(); + // Stable, so equal scores stay in first-seen order. + ranked.sort_by(|a, b| b.1.total_cmp(&a.1)); + + ranked + .into_iter() + .enumerate() + .map(|(at, (id, score))| { + let mut hit = fused[id].best.clone(); + hit.rank = at as u32 + 1; + hit.score = score; + hit + }) + .collect() +} + +struct Fusing<'a> { + best: &'a Hit, + score: f64, + seen: HashMap, +} + +#[cfg(test)] +mod tests { + use super::*; + + fn hit(id: &str, channel: Channel, rank: u32) -> Hit { + Hit::new(id, channel, rank, 0.0) + } + + #[test] + fn a_result_both_channels_like_beats_one_either_channel_loves() { + let hits = [ + hit("both", Channel::Vector, 2), + hit("vector-only", Channel::Vector, 1), + hit("both", Channel::Fts, 2), + hit("fts-only", Channel::Fts, 1), + ]; + let fused = fuse(&hits, &Weights::default()); + assert_eq!(fused[0].id, "both"); + assert_eq!(fused[0].rank, 1); + } + + #[test] + fn a_metadata_match_outranks_a_body_tie() { + let hits = [hit("title", Channel::Meta, 1), hit("body", Channel::Fts, 1)]; + let fused = fuse(&hits, &Weights::default()); + assert_eq!(fused[0].id, "title"); + } + + #[test] + fn the_scores_are_the_ones_the_method_defines() { + let hits = [hit("a", Channel::Vector, 1), hit("a", Channel::Fts, 3)]; + let fused = fuse(&hits, &Weights::default()); + assert_eq!(fused[0].score, 1.0 / 61.0 + 1.0 / 63.0); + } + + #[test] + fn the_record_that_comes_back_is_the_one_that_ranked_best() { + let mut best = hit("a", Channel::Vector, 1); + best.payload.insert("from".into(), "vector".into()); + let mut worse = hit("a", Channel::Fts, 9); + worse.payload.insert("from".into(), "fts".into()); + + let fused = fuse(&[worse, best], &Weights::default()); + assert_eq!(fused[0].payload["from"], "vector"); + assert_eq!(fused[0].channel, Channel::Vector); + } + + #[test] + fn ranks_come_out_consecutive_from_one() { + let hits = [ + hit("a", Channel::Vector, 1), + hit("b", Channel::Vector, 2), + hit("c", Channel::Vector, 3), + ]; + let fused = fuse(&hits, &Weights::default()); + assert_eq!( + fused.iter().map(|hit| hit.rank).collect::>(), + [1, 2, 3] + ); + } + + #[test] + fn a_tie_keeps_the_order_the_ids_arrived_in() { + let hits = [ + hit("second", Channel::Vector, 5), + hit("first", Channel::Fts, 5), + ]; + let fused = fuse(&hits, &Weights::default()); + assert_eq!(fused[0].id, "second"); + assert_eq!(fused[1].id, "first"); + assert_eq!(fused[0].score, fused[1].score); + } + + #[test] + fn a_channel_that_ranks_the_same_id_twice_counts_once() { + let twice = fuse( + &[hit("a", Channel::Vector, 9), hit("a", Channel::Vector, 1)], + &Weights::default(), + ); + let once = fuse(&[hit("a", Channel::Vector, 1)], &Weights::default()); + assert_eq!(twice[0].score, once[0].score); + } + + #[test] + fn weights_can_be_changed() { + let weights = Weights { + k: 1.0, + channels: HashMap::from([(Channel::Fts, 10.0)]), + }; + let fused = fuse( + &[hit("meta", Channel::Meta, 1), hit("fts", Channel::Fts, 1)], + &weights, + ); + assert_eq!(fused[0].id, "fts"); + assert_eq!(fused[0].score, 10.0 / 2.0); + } + + #[test] + fn nothing_in_nothing_out() { + assert!(fuse(&[], &Weights::default()).is_empty()); + } +} diff --git a/crates/rrf/src/main.rs b/crates/rrf/src/main.rs new file mode 100644 index 0000000..3522efe --- /dev/null +++ b/crates/rrf/src/main.rs @@ -0,0 +1,138 @@ +//! Reciprocal rank fusion: several ranked lists in, one ranked list out. + +use std::io::{BufWriter, Read, Write}; +use std::path::PathBuf; +use std::process::ExitCode; + +use clap::Parser; +use corpus_core::{Channel, Hit, exit, ndjson}; +use rrf::Weights; + +/// Fuse several ranked lists of hits into one. +/// +/// Reads hit records on stdin, in any order and interleaved if you like — +/// each carries the rank its own channel gave it, so the stream does not have +/// to be grouped. Writes the fused list, best first, with `rank` and `score` +/// replaced by the fused ones. +/// +/// Database-agnostic on purpose. By the time a hit reaches here it is already +/// yours, so this is the one tool in the suite that is worth calling in +/// process rather than as a subprocess. +#[derive(Parser)] +#[command( + version, + about, + after_help = "\ +Example: + + cat vector.ndjson fts.ndjson | rrf --limit 10 + +Weights are given as channel=number, and default to vec=1, fts=1, meta=1.5 — +a metadata match outranks a body-text match at the same rank, because a book +rarely repeats its own title in its body." +)] +struct Args { + /// Hit stream to read. Reads stdin when absent. + path: Option, + + /// How many results to keep. All of them when absent. + #[arg(long, short = 'n', value_name = "COUNT")] + limit: Option, + + /// The rank constant. + #[arg(long = "rrf-k", default_value_t = rrf::K, value_name = "K")] + k: f64, + + /// What a channel's rank is worth, as `channel=number`. Repeatable. + #[arg(long, value_name = "CHANNEL=WEIGHT")] + weight: Vec, +} + +fn main() -> ExitCode { + let args = Args::parse(); + + let weights = match weights(&args) { + Ok(weights) => weights, + Err(why) => { + eprintln!("rrf: {why}"); + return ExitCode::from(exit::USAGE); + } + }; + + let hits: Result, _> = match args.path.as_deref() { + Some(path) => match std::fs::File::open(path) { + Ok(file) => ndjson::read(std::io::BufReader::new(file)).collect(), + Err(e) => { + eprintln!("rrf: {}: {e}", path.display()); + return ExitCode::from(exit::IO); + } + }, + None => { + let mut text = Vec::new(); + if let Err(e) = std::io::stdin().read_to_end(&mut text) { + eprintln!("rrf: {e}"); + return ExitCode::from(exit::IO); + } + ndjson::read(text.as_slice()).collect() + } + }; + let hits = match hits { + Ok(hits) => hits, + Err(e) => { + eprintln!("rrf: {e}"); + return ExitCode::from(if e.is_broken_pipe() { + exit::OK + } else { + exit::DATA + }); + } + }; + + let fused = rrf::fuse(&hits, &weights); + let fused = &fused[..args.limit.unwrap_or(fused.len()).min(fused.len())]; + + let mut out = BufWriter::new(std::io::stdout().lock()); + for hit in fused { + if let Err(e) = ndjson::write_line(&mut out, hit) { + // A consumer that stopped reading is not a failure. + if e.is_broken_pipe() { + return ExitCode::from(exit::OK); + } + eprintln!("rrf: {e}"); + return ExitCode::from(exit::IO); + } + } + match out.flush() { + Ok(()) => ExitCode::from(exit::OK), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => ExitCode::from(exit::OK), + Err(e) => { + eprintln!("rrf: {e}"); + ExitCode::from(exit::IO) + } + } +} + +fn weights(args: &Args) -> Result { + if !args.k.is_finite() || args.k <= 0.0 { + return Err(format!("--rrf-k {} must be a positive number", args.k)); + } + let mut weights = Weights { + k: args.k, + ..Weights::default() + }; + for given in &args.weight { + let (channel, weight) = given + .split_once('=') + .ok_or_else(|| format!("--weight {given:?} is not channel=number"))?; + let channel: Channel = serde_json::from_value(serde_json::Value::String(channel.into())) + .map_err(|_| format!("--weight {given:?} names no channel this knows"))?; + let weight: f64 = weight + .parse() + .map_err(|_| format!("--weight {given:?} is not channel=number"))?; + if !weight.is_finite() { + return Err(format!("--weight {given:?} is not a number")); + } + weights.channels.insert(channel, weight); + } + Ok(weights) +} diff --git a/parity/Cargo.toml b/parity/Cargo.toml index 7884b9d..afe43d3 100644 --- a/parity/Cargo.toml +++ b/parity/Cargo.toml @@ -19,6 +19,7 @@ cuekit.workspace = true cueseg.workspace = true mdscrub.workspace = true pdftriage.workspace = true +rrf.workspace = true corpus-core.workspace = true serde_json.workspace = true textkit.workspace = true diff --git a/parity/SOURCE.md b/parity/SOURCE.md index 6294271..af24b0a 100644 --- a/parity/SOURCE.md +++ b/parity/SOURCE.md @@ -8,6 +8,7 @@ in this directory are the answers that Python gave, at one commit: | `book-pipeline` | `7499e3257572968b437ecfdc79feccc91b96585b` | | `podcast-download-systemd` | `ab20139cd0defb400a0dd9ed9034e03c1d1e356d` | | `podcast-pipeline` | `4566302444a6aa3609bd0d6685323619bd33c72b` | +| `knowledge-mcp` | `3d02cd5f9985e09f7354178e1907dbc28a7436da` | Regenerating against a different commit will move the goldens. That is the point — the diff is the review. Record the new commit here in the same change. @@ -17,7 +18,7 @@ a checkout lives is a fact about one machine and must not be compiled in: ```sh CATENA_PARITY_PYTHON=/path/to/venv/bin/python -CATENA_PARITY_PYTHONPATH=/path/to/book-pipeline/src:/path/to/podcast-pipeline/src +CATENA_PARITY_PYTHONPATH=/path/to/book-pipeline/src:/path/to/podcast-pipeline/src:/path/to/knowledge-mcp/src CATENA_PARITY_NODE=node # only if it is not already on PATH ``` diff --git a/parity/cases/rrf/all-tied.ndjson b/parity/cases/rrf/all-tied.ndjson new file mode 100644 index 0000000..3284935 --- /dev/null +++ b/parity/cases/rrf/all-tied.ndjson @@ -0,0 +1,10 @@ +{"id": "id0", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id1", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id2", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id3", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id4", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id5", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id6", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id7", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id8", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id9", "channel": "vec", "rank": 5, "score": 0.0} diff --git a/parity/cases/rrf/deep.ndjson b/parity/cases/rrf/deep.ndjson new file mode 100644 index 0000000..1c47a76 --- /dev/null +++ b/parity/cases/rrf/deep.ndjson @@ -0,0 +1,1000 @@ +{"id": "id1", "channel": "vec", "rank": 1, "score": 0.0} +{"id": "id2", "channel": "vec", "rank": 2, "score": 0.0} +{"id": "id3", "channel": "vec", "rank": 3, "score": 0.0} +{"id": "id4", "channel": "vec", "rank": 4, "score": 0.0} +{"id": "id5", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id6", "channel": "vec", "rank": 6, "score": 0.0} +{"id": "id7", "channel": "vec", "rank": 7, "score": 0.0} +{"id": "id8", "channel": "vec", "rank": 8, "score": 0.0} +{"id": "id9", "channel": "vec", "rank": 9, "score": 0.0} +{"id": "id10", "channel": "vec", "rank": 10, "score": 0.0} +{"id": "id11", "channel": "vec", "rank": 11, "score": 0.0} +{"id": "id12", "channel": "vec", "rank": 12, "score": 0.0} +{"id": "id13", "channel": "vec", "rank": 13, "score": 0.0} +{"id": "id14", "channel": "vec", "rank": 14, "score": 0.0} +{"id": "id15", "channel": "vec", "rank": 15, "score": 0.0} +{"id": "id16", "channel": "vec", "rank": 16, "score": 0.0} +{"id": "id17", "channel": "vec", "rank": 17, "score": 0.0} +{"id": "id18", "channel": "vec", "rank": 18, "score": 0.0} +{"id": "id19", "channel": "vec", "rank": 19, "score": 0.0} +{"id": "id20", "channel": "vec", "rank": 20, "score": 0.0} +{"id": "id21", "channel": "vec", "rank": 21, "score": 0.0} +{"id": "id22", "channel": "vec", "rank": 22, "score": 0.0} +{"id": "id23", "channel": "vec", "rank": 23, "score": 0.0} +{"id": "id24", "channel": "vec", "rank": 24, "score": 0.0} +{"id": "id25", "channel": "vec", "rank": 25, "score": 0.0} +{"id": "id26", "channel": "vec", "rank": 26, "score": 0.0} +{"id": "id27", "channel": "vec", "rank": 27, "score": 0.0} +{"id": "id28", "channel": "vec", "rank": 28, "score": 0.0} +{"id": "id29", "channel": "vec", "rank": 29, "score": 0.0} +{"id": "id30", "channel": "vec", "rank": 30, "score": 0.0} +{"id": "id31", "channel": "vec", "rank": 31, "score": 0.0} +{"id": "id32", "channel": "vec", "rank": 32, "score": 0.0} +{"id": "id33", "channel": "vec", "rank": 33, "score": 0.0} +{"id": "id34", "channel": "vec", "rank": 34, "score": 0.0} +{"id": "id35", "channel": "vec", "rank": 35, "score": 0.0} +{"id": "id36", "channel": "vec", "rank": 36, "score": 0.0} +{"id": "id37", "channel": "vec", "rank": 37, "score": 0.0} +{"id": "id38", "channel": "vec", "rank": 38, "score": 0.0} +{"id": "id39", "channel": "vec", "rank": 39, "score": 0.0} +{"id": "id40", "channel": "vec", "rank": 40, "score": 0.0} +{"id": "id41", "channel": "vec", "rank": 41, "score": 0.0} +{"id": "id42", "channel": "vec", "rank": 42, "score": 0.0} +{"id": "id43", "channel": "vec", "rank": 43, "score": 0.0} +{"id": "id44", "channel": "vec", "rank": 44, "score": 0.0} +{"id": "id45", "channel": "vec", "rank": 45, "score": 0.0} +{"id": "id46", "channel": "vec", "rank": 46, "score": 0.0} +{"id": "id47", "channel": "vec", "rank": 47, "score": 0.0} +{"id": "id48", "channel": "vec", "rank": 48, "score": 0.0} +{"id": "id49", "channel": "vec", "rank": 49, "score": 0.0} +{"id": "id50", "channel": "vec", "rank": 50, "score": 0.0} +{"id": "id51", "channel": "vec", "rank": 51, "score": 0.0} +{"id": "id52", "channel": "vec", "rank": 52, "score": 0.0} +{"id": "id53", "channel": "vec", "rank": 53, "score": 0.0} +{"id": "id54", "channel": "vec", "rank": 54, "score": 0.0} +{"id": "id55", "channel": "vec", "rank": 55, "score": 0.0} +{"id": "id56", "channel": "vec", "rank": 56, "score": 0.0} +{"id": "id57", "channel": "vec", "rank": 57, "score": 0.0} +{"id": "id58", "channel": "vec", "rank": 58, "score": 0.0} +{"id": "id59", "channel": "vec", "rank": 59, "score": 0.0} +{"id": "id60", "channel": "vec", "rank": 60, "score": 0.0} +{"id": "id61", "channel": "vec", "rank": 61, "score": 0.0} +{"id": "id62", "channel": "vec", "rank": 62, "score": 0.0} +{"id": "id63", "channel": "vec", "rank": 63, "score": 0.0} +{"id": "id64", "channel": "vec", "rank": 64, "score": 0.0} +{"id": "id65", "channel": "vec", "rank": 65, "score": 0.0} +{"id": "id66", "channel": "vec", "rank": 66, "score": 0.0} +{"id": "id67", "channel": "vec", "rank": 67, "score": 0.0} +{"id": "id68", "channel": "vec", "rank": 68, "score": 0.0} +{"id": "id69", "channel": "vec", "rank": 69, "score": 0.0} +{"id": "id70", "channel": "vec", "rank": 70, "score": 0.0} +{"id": "id71", "channel": "vec", "rank": 71, "score": 0.0} +{"id": "id72", "channel": "vec", "rank": 72, "score": 0.0} +{"id": "id73", "channel": "vec", "rank": 73, "score": 0.0} +{"id": "id74", "channel": "vec", "rank": 74, "score": 0.0} +{"id": "id75", "channel": "vec", "rank": 75, "score": 0.0} +{"id": "id76", "channel": "vec", "rank": 76, "score": 0.0} +{"id": "id77", "channel": "vec", "rank": 77, "score": 0.0} +{"id": "id78", "channel": "vec", "rank": 78, "score": 0.0} +{"id": "id79", "channel": "vec", "rank": 79, "score": 0.0} +{"id": "id80", "channel": "vec", "rank": 80, "score": 0.0} +{"id": "id81", "channel": "vec", "rank": 81, "score": 0.0} +{"id": "id82", "channel": "vec", "rank": 82, "score": 0.0} +{"id": "id83", "channel": "vec", "rank": 83, "score": 0.0} +{"id": "id84", "channel": "vec", "rank": 84, "score": 0.0} +{"id": "id85", "channel": "vec", "rank": 85, "score": 0.0} +{"id": "id86", "channel": "vec", "rank": 86, "score": 0.0} +{"id": "id87", "channel": "vec", "rank": 87, "score": 0.0} +{"id": "id88", "channel": "vec", "rank": 88, "score": 0.0} +{"id": "id89", "channel": "vec", "rank": 89, "score": 0.0} +{"id": "id90", "channel": "vec", "rank": 90, "score": 0.0} +{"id": "id91", "channel": "vec", "rank": 91, "score": 0.0} +{"id": "id92", "channel": "vec", "rank": 92, "score": 0.0} +{"id": "id93", "channel": "vec", "rank": 93, "score": 0.0} +{"id": "id94", "channel": "vec", "rank": 94, "score": 0.0} +{"id": "id95", "channel": "vec", "rank": 95, "score": 0.0} +{"id": "id96", "channel": "vec", "rank": 96, "score": 0.0} +{"id": "id97", "channel": "vec", "rank": 97, "score": 0.0} +{"id": "id98", "channel": "vec", "rank": 98, "score": 0.0} +{"id": "id99", "channel": "vec", "rank": 99, "score": 0.0} +{"id": "id100", "channel": "vec", "rank": 100, "score": 0.0} +{"id": "id101", "channel": "vec", "rank": 101, "score": 0.0} +{"id": "id102", "channel": "vec", "rank": 102, "score": 0.0} +{"id": "id103", "channel": "vec", "rank": 103, "score": 0.0} +{"id": "id104", "channel": "vec", "rank": 104, "score": 0.0} +{"id": "id105", "channel": "vec", "rank": 105, "score": 0.0} +{"id": "id106", "channel": "vec", "rank": 106, "score": 0.0} +{"id": "id107", "channel": "vec", "rank": 107, "score": 0.0} +{"id": "id108", "channel": "vec", "rank": 108, "score": 0.0} +{"id": "id109", "channel": "vec", "rank": 109, "score": 0.0} +{"id": "id110", "channel": "vec", "rank": 110, "score": 0.0} +{"id": "id111", "channel": "vec", "rank": 111, "score": 0.0} +{"id": "id112", "channel": "vec", "rank": 112, "score": 0.0} +{"id": "id113", "channel": "vec", "rank": 113, "score": 0.0} +{"id": "id114", "channel": "vec", "rank": 114, "score": 0.0} +{"id": "id115", "channel": "vec", "rank": 115, "score": 0.0} +{"id": "id116", "channel": "vec", "rank": 116, "score": 0.0} +{"id": "id117", "channel": "vec", "rank": 117, "score": 0.0} +{"id": "id118", "channel": "vec", "rank": 118, "score": 0.0} +{"id": "id119", "channel": "vec", "rank": 119, "score": 0.0} +{"id": "id120", "channel": "vec", "rank": 120, "score": 0.0} +{"id": "id121", "channel": "vec", "rank": 121, "score": 0.0} +{"id": "id122", "channel": "vec", "rank": 122, "score": 0.0} +{"id": "id123", "channel": "vec", "rank": 123, "score": 0.0} +{"id": "id124", "channel": "vec", "rank": 124, "score": 0.0} +{"id": "id125", "channel": "vec", "rank": 125, "score": 0.0} +{"id": "id126", "channel": "vec", "rank": 126, "score": 0.0} +{"id": "id127", "channel": "vec", "rank": 127, "score": 0.0} +{"id": "id128", "channel": "vec", "rank": 128, "score": 0.0} +{"id": "id129", "channel": "vec", "rank": 129, "score": 0.0} +{"id": "id130", "channel": "vec", "rank": 130, "score": 0.0} +{"id": "id131", "channel": "vec", "rank": 131, "score": 0.0} +{"id": "id132", "channel": "vec", "rank": 132, "score": 0.0} +{"id": "id133", "channel": "vec", "rank": 133, "score": 0.0} +{"id": "id134", "channel": "vec", "rank": 134, "score": 0.0} +{"id": "id135", "channel": "vec", "rank": 135, "score": 0.0} +{"id": "id136", "channel": "vec", "rank": 136, "score": 0.0} +{"id": "id137", "channel": "vec", "rank": 137, "score": 0.0} +{"id": "id138", "channel": "vec", "rank": 138, "score": 0.0} +{"id": "id139", "channel": "vec", "rank": 139, "score": 0.0} +{"id": "id140", "channel": "vec", "rank": 140, "score": 0.0} +{"id": "id141", "channel": "vec", "rank": 141, "score": 0.0} +{"id": "id142", "channel": "vec", "rank": 142, "score": 0.0} +{"id": "id143", "channel": "vec", "rank": 143, "score": 0.0} +{"id": "id144", "channel": "vec", "rank": 144, "score": 0.0} +{"id": "id145", "channel": "vec", "rank": 145, "score": 0.0} +{"id": "id146", "channel": "vec", "rank": 146, "score": 0.0} +{"id": "id147", "channel": "vec", "rank": 147, "score": 0.0} +{"id": "id148", "channel": "vec", "rank": 148, "score": 0.0} +{"id": "id149", "channel": "vec", "rank": 149, "score": 0.0} +{"id": "id150", "channel": "vec", "rank": 150, "score": 0.0} +{"id": "id151", "channel": "vec", "rank": 151, "score": 0.0} +{"id": "id152", "channel": "vec", "rank": 152, "score": 0.0} +{"id": "id153", "channel": "vec", "rank": 153, "score": 0.0} +{"id": "id154", "channel": "vec", "rank": 154, "score": 0.0} +{"id": "id155", "channel": "vec", "rank": 155, "score": 0.0} +{"id": "id156", "channel": "vec", "rank": 156, "score": 0.0} +{"id": "id157", "channel": "vec", "rank": 157, "score": 0.0} +{"id": "id158", "channel": "vec", "rank": 158, "score": 0.0} +{"id": "id159", "channel": "vec", "rank": 159, "score": 0.0} +{"id": "id160", "channel": "vec", "rank": 160, "score": 0.0} +{"id": "id161", "channel": "vec", "rank": 161, "score": 0.0} +{"id": "id162", "channel": "vec", "rank": 162, "score": 0.0} +{"id": "id163", "channel": "vec", "rank": 163, "score": 0.0} +{"id": "id164", "channel": "vec", "rank": 164, "score": 0.0} +{"id": "id165", "channel": "vec", "rank": 165, "score": 0.0} +{"id": "id166", "channel": "vec", "rank": 166, "score": 0.0} +{"id": "id167", "channel": "vec", "rank": 167, "score": 0.0} +{"id": "id168", "channel": "vec", "rank": 168, "score": 0.0} +{"id": "id169", "channel": "vec", "rank": 169, "score": 0.0} +{"id": "id170", "channel": "vec", "rank": 170, "score": 0.0} +{"id": "id171", "channel": "vec", "rank": 171, "score": 0.0} +{"id": "id172", "channel": "vec", "rank": 172, "score": 0.0} +{"id": "id173", "channel": "vec", "rank": 173, "score": 0.0} +{"id": "id174", "channel": "vec", "rank": 174, "score": 0.0} +{"id": "id175", "channel": "vec", "rank": 175, "score": 0.0} +{"id": "id176", "channel": "vec", "rank": 176, "score": 0.0} +{"id": "id177", "channel": "vec", "rank": 177, "score": 0.0} +{"id": "id178", "channel": "vec", "rank": 178, "score": 0.0} +{"id": "id179", "channel": "vec", "rank": 179, "score": 0.0} +{"id": "id180", "channel": "vec", "rank": 180, "score": 0.0} +{"id": "id181", "channel": "vec", "rank": 181, "score": 0.0} +{"id": "id182", "channel": "vec", "rank": 182, "score": 0.0} +{"id": "id183", "channel": "vec", "rank": 183, "score": 0.0} +{"id": "id184", "channel": "vec", "rank": 184, "score": 0.0} +{"id": "id185", "channel": "vec", "rank": 185, "score": 0.0} +{"id": "id186", "channel": "vec", "rank": 186, "score": 0.0} +{"id": "id187", "channel": "vec", "rank": 187, "score": 0.0} +{"id": "id188", "channel": "vec", "rank": 188, "score": 0.0} +{"id": "id189", "channel": "vec", "rank": 189, "score": 0.0} +{"id": "id190", "channel": "vec", "rank": 190, "score": 0.0} +{"id": "id191", "channel": "vec", "rank": 191, "score": 0.0} +{"id": "id192", "channel": "vec", "rank": 192, "score": 0.0} +{"id": "id193", "channel": "vec", "rank": 193, "score": 0.0} +{"id": "id194", "channel": "vec", "rank": 194, "score": 0.0} +{"id": "id195", "channel": "vec", "rank": 195, "score": 0.0} +{"id": "id196", "channel": "vec", "rank": 196, "score": 0.0} +{"id": "id197", "channel": "vec", "rank": 197, "score": 0.0} +{"id": "id198", "channel": "vec", "rank": 198, "score": 0.0} +{"id": "id199", "channel": "vec", "rank": 199, "score": 0.0} +{"id": "id200", "channel": "vec", "rank": 200, "score": 0.0} +{"id": "id201", "channel": "vec", "rank": 201, "score": 0.0} +{"id": "id202", "channel": "vec", "rank": 202, "score": 0.0} +{"id": "id203", "channel": "vec", "rank": 203, "score": 0.0} +{"id": "id204", "channel": "vec", "rank": 204, "score": 0.0} +{"id": "id205", "channel": "vec", "rank": 205, "score": 0.0} +{"id": "id206", "channel": "vec", "rank": 206, "score": 0.0} +{"id": "id207", "channel": "vec", "rank": 207, "score": 0.0} +{"id": "id208", "channel": "vec", "rank": 208, "score": 0.0} +{"id": "id209", "channel": "vec", "rank": 209, "score": 0.0} +{"id": "id210", "channel": "vec", "rank": 210, "score": 0.0} +{"id": "id211", "channel": "vec", "rank": 211, "score": 0.0} +{"id": "id212", "channel": "vec", "rank": 212, "score": 0.0} +{"id": "id213", "channel": "vec", "rank": 213, "score": 0.0} +{"id": "id214", "channel": "vec", "rank": 214, "score": 0.0} +{"id": "id215", "channel": "vec", "rank": 215, "score": 0.0} +{"id": "id216", "channel": "vec", "rank": 216, "score": 0.0} +{"id": "id217", "channel": "vec", "rank": 217, "score": 0.0} +{"id": "id218", "channel": "vec", "rank": 218, "score": 0.0} +{"id": "id219", "channel": "vec", "rank": 219, "score": 0.0} +{"id": "id220", "channel": "vec", "rank": 220, "score": 0.0} +{"id": "id221", "channel": "vec", "rank": 221, "score": 0.0} +{"id": "id222", "channel": "vec", "rank": 222, "score": 0.0} +{"id": "id223", "channel": "vec", "rank": 223, "score": 0.0} +{"id": "id224", "channel": "vec", "rank": 224, "score": 0.0} +{"id": "id225", "channel": "vec", "rank": 225, "score": 0.0} +{"id": "id226", "channel": "vec", "rank": 226, "score": 0.0} +{"id": "id227", "channel": "vec", "rank": 227, "score": 0.0} +{"id": "id228", "channel": "vec", "rank": 228, "score": 0.0} +{"id": "id229", "channel": "vec", "rank": 229, "score": 0.0} +{"id": "id230", "channel": "vec", "rank": 230, "score": 0.0} +{"id": "id231", "channel": "vec", "rank": 231, "score": 0.0} +{"id": "id232", "channel": "vec", "rank": 232, "score": 0.0} +{"id": "id233", "channel": "vec", "rank": 233, "score": 0.0} +{"id": "id234", "channel": "vec", "rank": 234, "score": 0.0} +{"id": "id235", "channel": "vec", "rank": 235, "score": 0.0} +{"id": "id236", "channel": "vec", "rank": 236, "score": 0.0} +{"id": "id237", "channel": "vec", "rank": 237, "score": 0.0} +{"id": "id238", "channel": "vec", "rank": 238, "score": 0.0} +{"id": "id239", "channel": "vec", "rank": 239, "score": 0.0} +{"id": "id240", "channel": "vec", "rank": 240, "score": 0.0} +{"id": "id241", "channel": "vec", "rank": 241, "score": 0.0} +{"id": "id242", "channel": "vec", "rank": 242, "score": 0.0} +{"id": "id243", "channel": "vec", "rank": 243, "score": 0.0} +{"id": "id244", "channel": "vec", "rank": 244, "score": 0.0} +{"id": "id245", "channel": "vec", "rank": 245, "score": 0.0} +{"id": "id246", "channel": "vec", "rank": 246, "score": 0.0} +{"id": "id247", "channel": "vec", "rank": 247, "score": 0.0} +{"id": "id248", "channel": "vec", "rank": 248, "score": 0.0} +{"id": "id249", "channel": "vec", "rank": 249, "score": 0.0} +{"id": "id250", "channel": "vec", "rank": 250, "score": 0.0} +{"id": "id251", "channel": "vec", "rank": 251, "score": 0.0} +{"id": "id252", "channel": "vec", "rank": 252, "score": 0.0} +{"id": "id253", "channel": "vec", "rank": 253, "score": 0.0} +{"id": "id254", "channel": "vec", "rank": 254, "score": 0.0} +{"id": "id255", "channel": "vec", "rank": 255, "score": 0.0} +{"id": "id256", "channel": "vec", "rank": 256, "score": 0.0} +{"id": "id257", "channel": "vec", "rank": 257, "score": 0.0} +{"id": "id258", "channel": "vec", "rank": 258, "score": 0.0} +{"id": "id259", "channel": "vec", "rank": 259, "score": 0.0} +{"id": "id260", "channel": "vec", "rank": 260, "score": 0.0} +{"id": "id261", "channel": "vec", "rank": 261, "score": 0.0} +{"id": "id262", "channel": "vec", "rank": 262, "score": 0.0} +{"id": "id263", "channel": "vec", "rank": 263, "score": 0.0} +{"id": "id264", "channel": "vec", "rank": 264, "score": 0.0} +{"id": "id265", "channel": "vec", "rank": 265, "score": 0.0} +{"id": "id266", "channel": "vec", "rank": 266, "score": 0.0} +{"id": "id267", "channel": "vec", "rank": 267, "score": 0.0} +{"id": "id268", "channel": "vec", "rank": 268, "score": 0.0} +{"id": "id269", "channel": "vec", "rank": 269, "score": 0.0} +{"id": "id270", "channel": "vec", "rank": 270, "score": 0.0} +{"id": "id271", "channel": "vec", "rank": 271, "score": 0.0} +{"id": "id272", "channel": "vec", "rank": 272, "score": 0.0} +{"id": "id273", "channel": "vec", "rank": 273, "score": 0.0} +{"id": "id274", "channel": "vec", "rank": 274, "score": 0.0} +{"id": "id275", "channel": "vec", "rank": 275, "score": 0.0} +{"id": "id276", "channel": "vec", "rank": 276, "score": 0.0} +{"id": "id277", "channel": "vec", "rank": 277, "score": 0.0} +{"id": "id278", "channel": "vec", "rank": 278, "score": 0.0} +{"id": "id279", "channel": "vec", "rank": 279, "score": 0.0} +{"id": "id280", "channel": "vec", "rank": 280, "score": 0.0} +{"id": "id281", "channel": "vec", "rank": 281, "score": 0.0} +{"id": "id282", "channel": "vec", "rank": 282, "score": 0.0} +{"id": "id283", "channel": "vec", "rank": 283, "score": 0.0} +{"id": "id284", "channel": "vec", "rank": 284, "score": 0.0} +{"id": "id285", "channel": "vec", "rank": 285, "score": 0.0} +{"id": "id286", "channel": "vec", "rank": 286, "score": 0.0} +{"id": "id287", "channel": "vec", "rank": 287, "score": 0.0} +{"id": "id288", "channel": "vec", "rank": 288, "score": 0.0} +{"id": "id289", "channel": "vec", "rank": 289, "score": 0.0} +{"id": "id290", "channel": "vec", "rank": 290, "score": 0.0} +{"id": "id291", "channel": "vec", "rank": 291, "score": 0.0} +{"id": "id292", "channel": "vec", "rank": 292, "score": 0.0} +{"id": "id293", "channel": "vec", "rank": 293, "score": 0.0} +{"id": "id294", "channel": "vec", "rank": 294, "score": 0.0} +{"id": "id295", "channel": "vec", "rank": 295, "score": 0.0} +{"id": "id296", "channel": "vec", "rank": 296, "score": 0.0} +{"id": "id297", "channel": "vec", "rank": 297, "score": 0.0} +{"id": "id298", "channel": "vec", "rank": 298, "score": 0.0} +{"id": "id299", "channel": "vec", "rank": 299, "score": 0.0} +{"id": "id300", "channel": "vec", "rank": 300, "score": 0.0} +{"id": "id301", "channel": "vec", "rank": 301, "score": 0.0} +{"id": "id302", "channel": "vec", "rank": 302, "score": 0.0} +{"id": "id303", "channel": "vec", "rank": 303, "score": 0.0} +{"id": "id304", "channel": "vec", "rank": 304, "score": 0.0} +{"id": "id305", "channel": "vec", "rank": 305, "score": 0.0} +{"id": "id306", "channel": "vec", "rank": 306, "score": 0.0} +{"id": "id307", "channel": "vec", "rank": 307, "score": 0.0} +{"id": "id308", "channel": "vec", "rank": 308, "score": 0.0} +{"id": "id309", "channel": "vec", "rank": 309, "score": 0.0} +{"id": "id310", "channel": "vec", "rank": 310, "score": 0.0} +{"id": "id311", "channel": "vec", "rank": 311, "score": 0.0} +{"id": "id312", "channel": "vec", "rank": 312, "score": 0.0} +{"id": "id313", "channel": "vec", "rank": 313, "score": 0.0} +{"id": "id314", "channel": "vec", "rank": 314, "score": 0.0} +{"id": "id315", "channel": "vec", "rank": 315, "score": 0.0} +{"id": "id316", "channel": "vec", "rank": 316, "score": 0.0} +{"id": "id317", "channel": "vec", "rank": 317, "score": 0.0} +{"id": "id318", "channel": "vec", "rank": 318, "score": 0.0} +{"id": "id319", "channel": "vec", "rank": 319, "score": 0.0} +{"id": "id320", "channel": "vec", "rank": 320, "score": 0.0} +{"id": "id321", "channel": "vec", "rank": 321, "score": 0.0} +{"id": "id322", "channel": "vec", "rank": 322, "score": 0.0} +{"id": "id323", "channel": "vec", "rank": 323, "score": 0.0} +{"id": "id324", "channel": "vec", "rank": 324, "score": 0.0} +{"id": "id325", "channel": "vec", "rank": 325, "score": 0.0} +{"id": "id326", "channel": "vec", "rank": 326, "score": 0.0} +{"id": "id327", "channel": "vec", "rank": 327, "score": 0.0} +{"id": "id328", "channel": "vec", "rank": 328, "score": 0.0} +{"id": "id329", "channel": "vec", "rank": 329, "score": 0.0} +{"id": "id330", "channel": "vec", "rank": 330, "score": 0.0} +{"id": "id331", "channel": "vec", "rank": 331, "score": 0.0} +{"id": "id332", "channel": "vec", "rank": 332, "score": 0.0} +{"id": "id333", "channel": "vec", "rank": 333, "score": 0.0} +{"id": "id334", "channel": "vec", "rank": 334, "score": 0.0} +{"id": "id335", "channel": "vec", "rank": 335, "score": 0.0} +{"id": "id336", "channel": "vec", "rank": 336, "score": 0.0} +{"id": "id337", "channel": "vec", "rank": 337, "score": 0.0} +{"id": "id338", "channel": "vec", "rank": 338, "score": 0.0} +{"id": "id339", "channel": "vec", "rank": 339, "score": 0.0} +{"id": "id340", "channel": "vec", "rank": 340, "score": 0.0} +{"id": "id341", "channel": "vec", "rank": 341, "score": 0.0} +{"id": "id342", "channel": "vec", "rank": 342, "score": 0.0} +{"id": "id343", "channel": "vec", "rank": 343, "score": 0.0} +{"id": "id344", "channel": "vec", "rank": 344, "score": 0.0} +{"id": "id345", "channel": "vec", "rank": 345, "score": 0.0} +{"id": "id346", "channel": "vec", "rank": 346, "score": 0.0} +{"id": "id347", "channel": "vec", "rank": 347, "score": 0.0} +{"id": "id348", "channel": "vec", "rank": 348, "score": 0.0} +{"id": "id349", "channel": "vec", "rank": 349, "score": 0.0} +{"id": "id350", "channel": "vec", "rank": 350, "score": 0.0} +{"id": "id351", "channel": "vec", "rank": 351, "score": 0.0} +{"id": "id352", "channel": "vec", "rank": 352, "score": 0.0} +{"id": "id353", "channel": "vec", "rank": 353, "score": 0.0} +{"id": "id354", "channel": "vec", "rank": 354, "score": 0.0} +{"id": "id355", "channel": "vec", "rank": 355, "score": 0.0} +{"id": "id356", "channel": "vec", "rank": 356, "score": 0.0} +{"id": "id357", "channel": "vec", "rank": 357, "score": 0.0} +{"id": "id358", "channel": "vec", "rank": 358, "score": 0.0} +{"id": "id359", "channel": "vec", "rank": 359, "score": 0.0} +{"id": "id360", "channel": "vec", "rank": 360, "score": 0.0} +{"id": "id361", "channel": "vec", "rank": 361, "score": 0.0} +{"id": "id362", "channel": "vec", "rank": 362, "score": 0.0} +{"id": "id363", "channel": "vec", "rank": 363, "score": 0.0} +{"id": "id364", "channel": "vec", "rank": 364, "score": 0.0} +{"id": "id365", "channel": "vec", "rank": 365, "score": 0.0} +{"id": "id366", "channel": "vec", "rank": 366, "score": 0.0} +{"id": "id367", "channel": "vec", "rank": 367, "score": 0.0} +{"id": "id368", "channel": "vec", "rank": 368, "score": 0.0} +{"id": "id369", "channel": "vec", "rank": 369, "score": 0.0} +{"id": "id370", "channel": "vec", "rank": 370, "score": 0.0} +{"id": "id371", "channel": "vec", "rank": 371, "score": 0.0} +{"id": "id372", "channel": "vec", "rank": 372, "score": 0.0} +{"id": "id373", "channel": "vec", "rank": 373, "score": 0.0} +{"id": "id374", "channel": "vec", "rank": 374, "score": 0.0} +{"id": "id375", "channel": "vec", "rank": 375, "score": 0.0} +{"id": "id376", "channel": "vec", "rank": 376, "score": 0.0} +{"id": "id377", "channel": "vec", "rank": 377, "score": 0.0} +{"id": "id378", "channel": "vec", "rank": 378, "score": 0.0} +{"id": "id379", "channel": "vec", "rank": 379, "score": 0.0} +{"id": "id380", "channel": "vec", "rank": 380, "score": 0.0} +{"id": "id381", "channel": "vec", "rank": 381, "score": 0.0} +{"id": "id382", "channel": "vec", "rank": 382, "score": 0.0} +{"id": "id383", "channel": "vec", "rank": 383, "score": 0.0} +{"id": "id384", "channel": "vec", "rank": 384, "score": 0.0} +{"id": "id385", "channel": "vec", "rank": 385, "score": 0.0} +{"id": "id386", "channel": "vec", "rank": 386, "score": 0.0} +{"id": "id387", "channel": "vec", "rank": 387, "score": 0.0} +{"id": "id388", "channel": "vec", "rank": 388, "score": 0.0} +{"id": "id389", "channel": "vec", "rank": 389, "score": 0.0} +{"id": "id390", "channel": "vec", "rank": 390, "score": 0.0} +{"id": "id391", "channel": "vec", "rank": 391, "score": 0.0} +{"id": "id392", "channel": "vec", "rank": 392, "score": 0.0} +{"id": "id393", "channel": "vec", "rank": 393, "score": 0.0} +{"id": "id394", "channel": "vec", "rank": 394, "score": 0.0} +{"id": "id395", "channel": "vec", "rank": 395, "score": 0.0} +{"id": "id396", "channel": "vec", "rank": 396, "score": 0.0} +{"id": "id397", "channel": "vec", "rank": 397, "score": 0.0} +{"id": "id398", "channel": "vec", "rank": 398, "score": 0.0} +{"id": "id399", "channel": "vec", "rank": 399, "score": 0.0} +{"id": "id400", "channel": "vec", "rank": 400, "score": 0.0} +{"id": "id401", "channel": "vec", "rank": 401, "score": 0.0} +{"id": "id402", "channel": "vec", "rank": 402, "score": 0.0} +{"id": "id403", "channel": "vec", "rank": 403, "score": 0.0} +{"id": "id404", "channel": "vec", "rank": 404, "score": 0.0} +{"id": "id405", "channel": "vec", "rank": 405, "score": 0.0} +{"id": "id406", "channel": "vec", "rank": 406, "score": 0.0} +{"id": "id407", "channel": "vec", "rank": 407, "score": 0.0} +{"id": "id408", "channel": "vec", "rank": 408, "score": 0.0} +{"id": "id409", "channel": "vec", "rank": 409, "score": 0.0} +{"id": "id410", "channel": "vec", "rank": 410, "score": 0.0} +{"id": "id411", "channel": "vec", "rank": 411, "score": 0.0} +{"id": "id412", "channel": "vec", "rank": 412, "score": 0.0} +{"id": "id413", "channel": "vec", "rank": 413, "score": 0.0} +{"id": "id414", "channel": "vec", "rank": 414, "score": 0.0} +{"id": "id415", "channel": "vec", "rank": 415, "score": 0.0} +{"id": "id416", "channel": "vec", "rank": 416, "score": 0.0} +{"id": "id417", "channel": "vec", "rank": 417, "score": 0.0} +{"id": "id418", "channel": "vec", "rank": 418, "score": 0.0} +{"id": "id419", "channel": "vec", "rank": 419, "score": 0.0} +{"id": "id420", "channel": "vec", "rank": 420, "score": 0.0} +{"id": "id421", "channel": "vec", "rank": 421, "score": 0.0} +{"id": "id422", "channel": "vec", "rank": 422, "score": 0.0} +{"id": "id423", "channel": "vec", "rank": 423, "score": 0.0} +{"id": "id424", "channel": "vec", "rank": 424, "score": 0.0} +{"id": "id425", "channel": "vec", "rank": 425, "score": 0.0} +{"id": "id426", "channel": "vec", "rank": 426, "score": 0.0} +{"id": "id427", "channel": "vec", "rank": 427, "score": 0.0} +{"id": "id428", "channel": "vec", "rank": 428, "score": 0.0} +{"id": "id429", "channel": "vec", "rank": 429, "score": 0.0} +{"id": "id430", "channel": "vec", "rank": 430, "score": 0.0} +{"id": "id431", "channel": "vec", "rank": 431, "score": 0.0} +{"id": "id432", "channel": "vec", "rank": 432, "score": 0.0} +{"id": "id433", "channel": "vec", "rank": 433, "score": 0.0} +{"id": "id434", "channel": "vec", "rank": 434, "score": 0.0} +{"id": "id435", "channel": "vec", "rank": 435, "score": 0.0} +{"id": "id436", "channel": "vec", "rank": 436, "score": 0.0} +{"id": "id437", "channel": "vec", "rank": 437, "score": 0.0} +{"id": "id438", "channel": "vec", "rank": 438, "score": 0.0} +{"id": "id439", "channel": "vec", "rank": 439, "score": 0.0} +{"id": "id440", "channel": "vec", "rank": 440, "score": 0.0} +{"id": "id441", "channel": "vec", "rank": 441, "score": 0.0} +{"id": "id442", "channel": "vec", "rank": 442, "score": 0.0} +{"id": "id443", "channel": "vec", "rank": 443, "score": 0.0} +{"id": "id444", "channel": "vec", "rank": 444, "score": 0.0} +{"id": "id445", "channel": "vec", "rank": 445, "score": 0.0} +{"id": "id446", "channel": "vec", "rank": 446, "score": 0.0} +{"id": "id447", "channel": "vec", "rank": 447, "score": 0.0} +{"id": "id448", "channel": "vec", "rank": 448, "score": 0.0} +{"id": "id449", "channel": "vec", "rank": 449, "score": 0.0} +{"id": "id450", "channel": "vec", "rank": 450, "score": 0.0} +{"id": "id451", "channel": "vec", "rank": 451, "score": 0.0} +{"id": "id452", "channel": "vec", "rank": 452, "score": 0.0} +{"id": "id453", "channel": "vec", "rank": 453, "score": 0.0} +{"id": "id454", "channel": "vec", "rank": 454, "score": 0.0} +{"id": "id455", "channel": "vec", "rank": 455, "score": 0.0} +{"id": "id456", "channel": "vec", "rank": 456, "score": 0.0} +{"id": "id457", "channel": "vec", "rank": 457, "score": 0.0} +{"id": "id458", "channel": "vec", "rank": 458, "score": 0.0} +{"id": "id459", "channel": "vec", "rank": 459, "score": 0.0} +{"id": "id460", "channel": "vec", "rank": 460, "score": 0.0} +{"id": "id461", "channel": "vec", "rank": 461, "score": 0.0} +{"id": "id462", "channel": "vec", "rank": 462, "score": 0.0} +{"id": "id463", "channel": "vec", "rank": 463, "score": 0.0} +{"id": "id464", "channel": "vec", "rank": 464, "score": 0.0} +{"id": "id465", "channel": "vec", "rank": 465, "score": 0.0} +{"id": "id466", "channel": "vec", "rank": 466, "score": 0.0} +{"id": "id467", "channel": "vec", "rank": 467, "score": 0.0} +{"id": "id468", "channel": "vec", "rank": 468, "score": 0.0} +{"id": "id469", "channel": "vec", "rank": 469, "score": 0.0} +{"id": "id470", "channel": "vec", "rank": 470, "score": 0.0} +{"id": "id471", "channel": "vec", "rank": 471, "score": 0.0} +{"id": "id472", "channel": "vec", "rank": 472, "score": 0.0} +{"id": "id473", "channel": "vec", "rank": 473, "score": 0.0} +{"id": "id474", "channel": "vec", "rank": 474, "score": 0.0} +{"id": "id475", "channel": "vec", "rank": 475, "score": 0.0} +{"id": "id476", "channel": "vec", "rank": 476, "score": 0.0} +{"id": "id477", "channel": "vec", "rank": 477, "score": 0.0} +{"id": "id478", "channel": "vec", "rank": 478, "score": 0.0} +{"id": "id479", "channel": "vec", "rank": 479, "score": 0.0} +{"id": "id480", "channel": "vec", "rank": 480, "score": 0.0} +{"id": "id481", "channel": "vec", "rank": 481, "score": 0.0} +{"id": "id482", "channel": "vec", "rank": 482, "score": 0.0} +{"id": "id483", "channel": "vec", "rank": 483, "score": 0.0} +{"id": "id484", "channel": "vec", "rank": 484, "score": 0.0} +{"id": "id485", "channel": "vec", "rank": 485, "score": 0.0} +{"id": "id486", "channel": "vec", "rank": 486, "score": 0.0} +{"id": "id487", "channel": "vec", "rank": 487, "score": 0.0} +{"id": "id488", "channel": "vec", "rank": 488, "score": 0.0} +{"id": "id489", "channel": "vec", "rank": 489, "score": 0.0} +{"id": "id490", "channel": "vec", "rank": 490, "score": 0.0} +{"id": "id491", "channel": "vec", "rank": 491, "score": 0.0} +{"id": "id492", "channel": "vec", "rank": 492, "score": 0.0} +{"id": "id493", "channel": "vec", "rank": 493, "score": 0.0} +{"id": "id494", "channel": "vec", "rank": 494, "score": 0.0} +{"id": "id495", "channel": "vec", "rank": 495, "score": 0.0} +{"id": "id496", "channel": "vec", "rank": 496, "score": 0.0} +{"id": "id497", "channel": "vec", "rank": 497, "score": 0.0} +{"id": "id498", "channel": "vec", "rank": 498, "score": 0.0} +{"id": "id499", "channel": "vec", "rank": 499, "score": 0.0} +{"id": "id500", "channel": "vec", "rank": 500, "score": 0.0} +{"id": "id1", "channel": "fts", "rank": 500, "score": 0.0} +{"id": "id2", "channel": "fts", "rank": 499, "score": 0.0} +{"id": "id3", "channel": "fts", "rank": 498, "score": 0.0} +{"id": "id4", "channel": "fts", "rank": 497, "score": 0.0} +{"id": "id5", "channel": "fts", "rank": 496, "score": 0.0} +{"id": "id6", "channel": "fts", "rank": 495, "score": 0.0} +{"id": "id7", "channel": "fts", "rank": 494, "score": 0.0} +{"id": "id8", "channel": "fts", "rank": 493, "score": 0.0} +{"id": "id9", "channel": "fts", "rank": 492, "score": 0.0} +{"id": "id10", "channel": "fts", "rank": 491, "score": 0.0} +{"id": "id11", "channel": "fts", "rank": 490, "score": 0.0} +{"id": "id12", "channel": "fts", "rank": 489, "score": 0.0} +{"id": "id13", "channel": "fts", "rank": 488, "score": 0.0} +{"id": "id14", "channel": "fts", "rank": 487, "score": 0.0} +{"id": "id15", "channel": "fts", "rank": 486, "score": 0.0} +{"id": "id16", "channel": "fts", "rank": 485, "score": 0.0} +{"id": "id17", "channel": "fts", "rank": 484, "score": 0.0} +{"id": "id18", "channel": "fts", "rank": 483, "score": 0.0} +{"id": "id19", "channel": "fts", "rank": 482, "score": 0.0} +{"id": "id20", "channel": "fts", "rank": 481, "score": 0.0} +{"id": "id21", "channel": "fts", "rank": 480, "score": 0.0} +{"id": "id22", "channel": "fts", "rank": 479, "score": 0.0} +{"id": "id23", "channel": "fts", "rank": 478, "score": 0.0} +{"id": "id24", "channel": "fts", "rank": 477, "score": 0.0} +{"id": "id25", "channel": "fts", "rank": 476, "score": 0.0} +{"id": "id26", "channel": "fts", "rank": 475, "score": 0.0} +{"id": "id27", "channel": "fts", "rank": 474, "score": 0.0} +{"id": "id28", "channel": "fts", "rank": 473, "score": 0.0} +{"id": "id29", "channel": "fts", "rank": 472, "score": 0.0} +{"id": "id30", "channel": "fts", "rank": 471, "score": 0.0} +{"id": "id31", "channel": "fts", "rank": 470, "score": 0.0} +{"id": "id32", "channel": "fts", "rank": 469, "score": 0.0} +{"id": "id33", "channel": "fts", "rank": 468, "score": 0.0} +{"id": "id34", "channel": "fts", "rank": 467, "score": 0.0} +{"id": "id35", "channel": "fts", "rank": 466, "score": 0.0} +{"id": "id36", "channel": "fts", "rank": 465, "score": 0.0} +{"id": "id37", "channel": "fts", "rank": 464, "score": 0.0} +{"id": "id38", "channel": "fts", "rank": 463, "score": 0.0} +{"id": "id39", "channel": "fts", "rank": 462, "score": 0.0} +{"id": "id40", "channel": "fts", "rank": 461, "score": 0.0} +{"id": "id41", "channel": "fts", "rank": 460, "score": 0.0} +{"id": "id42", "channel": "fts", "rank": 459, "score": 0.0} +{"id": "id43", "channel": "fts", "rank": 458, "score": 0.0} +{"id": "id44", "channel": "fts", "rank": 457, "score": 0.0} +{"id": "id45", "channel": "fts", "rank": 456, "score": 0.0} +{"id": "id46", "channel": "fts", "rank": 455, "score": 0.0} +{"id": "id47", "channel": "fts", "rank": 454, "score": 0.0} +{"id": "id48", "channel": "fts", "rank": 453, "score": 0.0} +{"id": "id49", "channel": "fts", "rank": 452, "score": 0.0} +{"id": "id50", "channel": "fts", "rank": 451, "score": 0.0} +{"id": "id51", "channel": "fts", "rank": 450, "score": 0.0} +{"id": "id52", "channel": "fts", "rank": 449, "score": 0.0} +{"id": "id53", "channel": "fts", "rank": 448, "score": 0.0} +{"id": "id54", "channel": "fts", "rank": 447, "score": 0.0} +{"id": "id55", "channel": "fts", "rank": 446, "score": 0.0} +{"id": "id56", "channel": "fts", "rank": 445, "score": 0.0} +{"id": "id57", "channel": "fts", "rank": 444, "score": 0.0} +{"id": "id58", "channel": "fts", "rank": 443, "score": 0.0} +{"id": "id59", "channel": "fts", "rank": 442, "score": 0.0} +{"id": "id60", "channel": "fts", "rank": 441, "score": 0.0} +{"id": "id61", "channel": "fts", "rank": 440, "score": 0.0} +{"id": "id62", "channel": "fts", "rank": 439, "score": 0.0} +{"id": "id63", "channel": "fts", "rank": 438, "score": 0.0} +{"id": "id64", "channel": "fts", "rank": 437, "score": 0.0} +{"id": "id65", "channel": "fts", "rank": 436, "score": 0.0} +{"id": "id66", "channel": "fts", "rank": 435, "score": 0.0} +{"id": "id67", "channel": "fts", "rank": 434, "score": 0.0} +{"id": "id68", "channel": "fts", "rank": 433, "score": 0.0} +{"id": "id69", "channel": "fts", "rank": 432, "score": 0.0} +{"id": "id70", "channel": "fts", "rank": 431, "score": 0.0} +{"id": "id71", "channel": "fts", "rank": 430, "score": 0.0} +{"id": "id72", "channel": "fts", "rank": 429, "score": 0.0} +{"id": "id73", "channel": "fts", "rank": 428, "score": 0.0} +{"id": "id74", "channel": "fts", "rank": 427, "score": 0.0} +{"id": "id75", "channel": "fts", "rank": 426, "score": 0.0} +{"id": "id76", "channel": "fts", "rank": 425, "score": 0.0} +{"id": "id77", "channel": "fts", "rank": 424, "score": 0.0} +{"id": "id78", "channel": "fts", "rank": 423, "score": 0.0} +{"id": "id79", "channel": "fts", "rank": 422, "score": 0.0} +{"id": "id80", "channel": "fts", "rank": 421, "score": 0.0} +{"id": "id81", "channel": "fts", "rank": 420, "score": 0.0} +{"id": "id82", "channel": "fts", "rank": 419, "score": 0.0} +{"id": "id83", "channel": "fts", "rank": 418, "score": 0.0} +{"id": "id84", "channel": "fts", "rank": 417, "score": 0.0} +{"id": "id85", "channel": "fts", "rank": 416, "score": 0.0} +{"id": "id86", "channel": "fts", "rank": 415, "score": 0.0} +{"id": "id87", "channel": "fts", "rank": 414, "score": 0.0} +{"id": "id88", "channel": "fts", "rank": 413, "score": 0.0} +{"id": "id89", "channel": "fts", "rank": 412, "score": 0.0} +{"id": "id90", "channel": "fts", "rank": 411, "score": 0.0} +{"id": "id91", "channel": "fts", "rank": 410, "score": 0.0} +{"id": "id92", "channel": "fts", "rank": 409, "score": 0.0} +{"id": "id93", "channel": "fts", "rank": 408, "score": 0.0} +{"id": "id94", "channel": "fts", "rank": 407, "score": 0.0} +{"id": "id95", "channel": "fts", "rank": 406, "score": 0.0} +{"id": "id96", "channel": "fts", "rank": 405, "score": 0.0} +{"id": "id97", "channel": "fts", "rank": 404, "score": 0.0} +{"id": "id98", "channel": "fts", "rank": 403, "score": 0.0} +{"id": "id99", "channel": "fts", "rank": 402, "score": 0.0} +{"id": "id100", "channel": "fts", "rank": 401, "score": 0.0} +{"id": "id101", "channel": "fts", "rank": 400, "score": 0.0} +{"id": "id102", "channel": "fts", "rank": 399, "score": 0.0} +{"id": "id103", "channel": "fts", "rank": 398, "score": 0.0} +{"id": "id104", "channel": "fts", "rank": 397, "score": 0.0} +{"id": "id105", "channel": "fts", "rank": 396, "score": 0.0} +{"id": "id106", "channel": "fts", "rank": 395, "score": 0.0} +{"id": "id107", "channel": "fts", "rank": 394, "score": 0.0} +{"id": "id108", "channel": "fts", "rank": 393, "score": 0.0} +{"id": "id109", "channel": "fts", "rank": 392, "score": 0.0} +{"id": "id110", "channel": "fts", "rank": 391, "score": 0.0} +{"id": "id111", "channel": "fts", "rank": 390, "score": 0.0} +{"id": "id112", "channel": "fts", "rank": 389, "score": 0.0} +{"id": "id113", "channel": "fts", "rank": 388, "score": 0.0} +{"id": "id114", "channel": "fts", "rank": 387, "score": 0.0} +{"id": "id115", "channel": "fts", "rank": 386, "score": 0.0} +{"id": "id116", "channel": "fts", "rank": 385, "score": 0.0} +{"id": "id117", "channel": "fts", "rank": 384, "score": 0.0} +{"id": "id118", "channel": "fts", "rank": 383, "score": 0.0} +{"id": "id119", "channel": "fts", "rank": 382, "score": 0.0} +{"id": "id120", "channel": "fts", "rank": 381, "score": 0.0} +{"id": "id121", "channel": "fts", "rank": 380, "score": 0.0} +{"id": "id122", "channel": "fts", "rank": 379, "score": 0.0} +{"id": "id123", "channel": "fts", "rank": 378, "score": 0.0} +{"id": "id124", "channel": "fts", "rank": 377, "score": 0.0} +{"id": "id125", "channel": "fts", "rank": 376, "score": 0.0} +{"id": "id126", "channel": "fts", "rank": 375, "score": 0.0} +{"id": "id127", "channel": "fts", "rank": 374, "score": 0.0} +{"id": "id128", "channel": "fts", "rank": 373, "score": 0.0} +{"id": "id129", "channel": "fts", "rank": 372, "score": 0.0} +{"id": "id130", "channel": "fts", "rank": 371, "score": 0.0} +{"id": "id131", "channel": "fts", "rank": 370, "score": 0.0} +{"id": "id132", "channel": "fts", "rank": 369, "score": 0.0} +{"id": "id133", "channel": "fts", "rank": 368, "score": 0.0} +{"id": "id134", "channel": "fts", "rank": 367, "score": 0.0} +{"id": "id135", "channel": "fts", "rank": 366, "score": 0.0} +{"id": "id136", "channel": "fts", "rank": 365, "score": 0.0} +{"id": "id137", "channel": "fts", "rank": 364, "score": 0.0} +{"id": "id138", "channel": "fts", "rank": 363, "score": 0.0} +{"id": "id139", "channel": "fts", "rank": 362, "score": 0.0} +{"id": "id140", "channel": "fts", "rank": 361, "score": 0.0} +{"id": "id141", "channel": "fts", "rank": 360, "score": 0.0} +{"id": "id142", "channel": "fts", "rank": 359, "score": 0.0} +{"id": "id143", "channel": "fts", "rank": 358, "score": 0.0} +{"id": "id144", "channel": "fts", "rank": 357, "score": 0.0} +{"id": "id145", "channel": "fts", "rank": 356, "score": 0.0} +{"id": "id146", "channel": "fts", "rank": 355, "score": 0.0} +{"id": "id147", "channel": "fts", "rank": 354, "score": 0.0} +{"id": "id148", "channel": "fts", "rank": 353, "score": 0.0} +{"id": "id149", "channel": "fts", "rank": 352, "score": 0.0} +{"id": "id150", "channel": "fts", "rank": 351, "score": 0.0} +{"id": "id151", "channel": "fts", "rank": 350, "score": 0.0} +{"id": "id152", "channel": "fts", "rank": 349, "score": 0.0} +{"id": "id153", "channel": "fts", "rank": 348, "score": 0.0} +{"id": "id154", "channel": "fts", "rank": 347, "score": 0.0} +{"id": "id155", "channel": "fts", "rank": 346, "score": 0.0} +{"id": "id156", "channel": "fts", "rank": 345, "score": 0.0} +{"id": "id157", "channel": "fts", "rank": 344, "score": 0.0} +{"id": "id158", "channel": "fts", "rank": 343, "score": 0.0} +{"id": "id159", "channel": "fts", "rank": 342, "score": 0.0} +{"id": "id160", "channel": "fts", "rank": 341, "score": 0.0} +{"id": "id161", "channel": "fts", "rank": 340, "score": 0.0} +{"id": "id162", "channel": "fts", "rank": 339, "score": 0.0} +{"id": "id163", "channel": "fts", "rank": 338, "score": 0.0} +{"id": "id164", "channel": "fts", "rank": 337, "score": 0.0} +{"id": "id165", "channel": "fts", "rank": 336, "score": 0.0} +{"id": "id166", "channel": "fts", "rank": 335, "score": 0.0} +{"id": "id167", "channel": "fts", "rank": 334, "score": 0.0} +{"id": "id168", "channel": "fts", "rank": 333, "score": 0.0} +{"id": "id169", "channel": "fts", "rank": 332, "score": 0.0} +{"id": "id170", "channel": "fts", "rank": 331, "score": 0.0} +{"id": "id171", "channel": "fts", "rank": 330, "score": 0.0} +{"id": "id172", "channel": "fts", "rank": 329, "score": 0.0} +{"id": "id173", "channel": "fts", "rank": 328, "score": 0.0} +{"id": "id174", "channel": "fts", "rank": 327, "score": 0.0} +{"id": "id175", "channel": "fts", "rank": 326, "score": 0.0} +{"id": "id176", "channel": "fts", "rank": 325, "score": 0.0} +{"id": "id177", "channel": "fts", "rank": 324, "score": 0.0} +{"id": "id178", "channel": "fts", "rank": 323, "score": 0.0} +{"id": "id179", "channel": "fts", "rank": 322, "score": 0.0} +{"id": "id180", "channel": "fts", "rank": 321, "score": 0.0} +{"id": "id181", "channel": "fts", "rank": 320, "score": 0.0} +{"id": "id182", "channel": "fts", "rank": 319, "score": 0.0} +{"id": "id183", "channel": "fts", "rank": 318, "score": 0.0} +{"id": "id184", "channel": "fts", "rank": 317, "score": 0.0} +{"id": "id185", "channel": "fts", "rank": 316, "score": 0.0} +{"id": "id186", "channel": "fts", "rank": 315, "score": 0.0} +{"id": "id187", "channel": "fts", "rank": 314, "score": 0.0} +{"id": "id188", "channel": "fts", "rank": 313, "score": 0.0} +{"id": "id189", "channel": "fts", "rank": 312, "score": 0.0} +{"id": "id190", "channel": "fts", "rank": 311, "score": 0.0} +{"id": "id191", "channel": "fts", "rank": 310, "score": 0.0} +{"id": "id192", "channel": "fts", "rank": 309, "score": 0.0} +{"id": "id193", "channel": "fts", "rank": 308, "score": 0.0} +{"id": "id194", "channel": "fts", "rank": 307, "score": 0.0} +{"id": "id195", "channel": "fts", "rank": 306, "score": 0.0} +{"id": "id196", "channel": "fts", "rank": 305, "score": 0.0} +{"id": "id197", "channel": "fts", "rank": 304, "score": 0.0} +{"id": "id198", "channel": "fts", "rank": 303, "score": 0.0} +{"id": "id199", "channel": "fts", "rank": 302, "score": 0.0} +{"id": "id200", "channel": "fts", "rank": 301, "score": 0.0} +{"id": "id201", "channel": "fts", "rank": 300, "score": 0.0} +{"id": "id202", "channel": "fts", "rank": 299, "score": 0.0} +{"id": "id203", "channel": "fts", "rank": 298, "score": 0.0} +{"id": "id204", "channel": "fts", "rank": 297, "score": 0.0} +{"id": "id205", "channel": "fts", "rank": 296, "score": 0.0} +{"id": "id206", "channel": "fts", "rank": 295, "score": 0.0} +{"id": "id207", "channel": "fts", "rank": 294, "score": 0.0} +{"id": "id208", "channel": "fts", "rank": 293, "score": 0.0} +{"id": "id209", "channel": "fts", "rank": 292, "score": 0.0} +{"id": "id210", "channel": "fts", "rank": 291, "score": 0.0} +{"id": "id211", "channel": "fts", "rank": 290, "score": 0.0} +{"id": "id212", "channel": "fts", "rank": 289, "score": 0.0} +{"id": "id213", "channel": "fts", "rank": 288, "score": 0.0} +{"id": "id214", "channel": "fts", "rank": 287, "score": 0.0} +{"id": "id215", "channel": "fts", "rank": 286, "score": 0.0} +{"id": "id216", "channel": "fts", "rank": 285, "score": 0.0} +{"id": "id217", "channel": "fts", "rank": 284, "score": 0.0} +{"id": "id218", "channel": "fts", "rank": 283, "score": 0.0} +{"id": "id219", "channel": "fts", "rank": 282, "score": 0.0} +{"id": "id220", "channel": "fts", "rank": 281, "score": 0.0} +{"id": "id221", "channel": "fts", "rank": 280, "score": 0.0} +{"id": "id222", "channel": "fts", "rank": 279, "score": 0.0} +{"id": "id223", "channel": "fts", "rank": 278, "score": 0.0} +{"id": "id224", "channel": "fts", "rank": 277, "score": 0.0} +{"id": "id225", "channel": "fts", "rank": 276, "score": 0.0} +{"id": "id226", "channel": "fts", "rank": 275, "score": 0.0} +{"id": "id227", "channel": "fts", "rank": 274, "score": 0.0} +{"id": "id228", "channel": "fts", "rank": 273, "score": 0.0} +{"id": "id229", "channel": "fts", "rank": 272, "score": 0.0} +{"id": "id230", "channel": "fts", "rank": 271, "score": 0.0} +{"id": "id231", "channel": "fts", "rank": 270, "score": 0.0} +{"id": "id232", "channel": "fts", "rank": 269, "score": 0.0} +{"id": "id233", "channel": "fts", "rank": 268, "score": 0.0} +{"id": "id234", "channel": "fts", "rank": 267, "score": 0.0} +{"id": "id235", "channel": "fts", "rank": 266, "score": 0.0} +{"id": "id236", "channel": "fts", "rank": 265, "score": 0.0} +{"id": "id237", "channel": "fts", "rank": 264, "score": 0.0} +{"id": "id238", "channel": "fts", "rank": 263, "score": 0.0} +{"id": "id239", "channel": "fts", "rank": 262, "score": 0.0} +{"id": "id240", "channel": "fts", "rank": 261, "score": 0.0} +{"id": "id241", "channel": "fts", "rank": 260, "score": 0.0} +{"id": "id242", "channel": "fts", "rank": 259, "score": 0.0} +{"id": "id243", "channel": "fts", "rank": 258, "score": 0.0} +{"id": "id244", "channel": "fts", "rank": 257, "score": 0.0} +{"id": "id245", "channel": "fts", "rank": 256, "score": 0.0} +{"id": "id246", "channel": "fts", "rank": 255, "score": 0.0} +{"id": "id247", "channel": "fts", "rank": 254, "score": 0.0} +{"id": "id248", "channel": "fts", "rank": 253, "score": 0.0} +{"id": "id249", "channel": "fts", "rank": 252, "score": 0.0} +{"id": "id250", "channel": "fts", "rank": 251, "score": 0.0} +{"id": "id251", "channel": "fts", "rank": 250, "score": 0.0} +{"id": "id252", "channel": "fts", "rank": 249, "score": 0.0} +{"id": "id253", "channel": "fts", "rank": 248, "score": 0.0} +{"id": "id254", "channel": "fts", "rank": 247, "score": 0.0} +{"id": "id255", "channel": "fts", "rank": 246, "score": 0.0} +{"id": "id256", "channel": "fts", "rank": 245, "score": 0.0} +{"id": "id257", "channel": "fts", "rank": 244, "score": 0.0} +{"id": "id258", "channel": "fts", "rank": 243, "score": 0.0} +{"id": "id259", "channel": "fts", "rank": 242, "score": 0.0} +{"id": "id260", "channel": "fts", "rank": 241, "score": 0.0} +{"id": "id261", "channel": "fts", "rank": 240, "score": 0.0} +{"id": "id262", "channel": "fts", "rank": 239, "score": 0.0} +{"id": "id263", "channel": "fts", "rank": 238, "score": 0.0} +{"id": "id264", "channel": "fts", "rank": 237, "score": 0.0} +{"id": "id265", "channel": "fts", "rank": 236, "score": 0.0} +{"id": "id266", "channel": "fts", "rank": 235, "score": 0.0} +{"id": "id267", "channel": "fts", "rank": 234, "score": 0.0} +{"id": "id268", "channel": "fts", "rank": 233, "score": 0.0} +{"id": "id269", "channel": "fts", "rank": 232, "score": 0.0} +{"id": "id270", "channel": "fts", "rank": 231, "score": 0.0} +{"id": "id271", "channel": "fts", "rank": 230, "score": 0.0} +{"id": "id272", "channel": "fts", "rank": 229, "score": 0.0} +{"id": "id273", "channel": "fts", "rank": 228, "score": 0.0} +{"id": "id274", "channel": "fts", "rank": 227, "score": 0.0} +{"id": "id275", "channel": "fts", "rank": 226, "score": 0.0} +{"id": "id276", "channel": "fts", "rank": 225, "score": 0.0} +{"id": "id277", "channel": "fts", "rank": 224, "score": 0.0} +{"id": "id278", "channel": "fts", "rank": 223, "score": 0.0} +{"id": "id279", "channel": "fts", "rank": 222, "score": 0.0} +{"id": "id280", "channel": "fts", "rank": 221, "score": 0.0} +{"id": "id281", "channel": "fts", "rank": 220, "score": 0.0} +{"id": "id282", "channel": "fts", "rank": 219, "score": 0.0} +{"id": "id283", "channel": "fts", "rank": 218, "score": 0.0} +{"id": "id284", "channel": "fts", "rank": 217, "score": 0.0} +{"id": "id285", "channel": "fts", "rank": 216, "score": 0.0} +{"id": "id286", "channel": "fts", "rank": 215, "score": 0.0} +{"id": "id287", "channel": "fts", "rank": 214, "score": 0.0} +{"id": "id288", "channel": "fts", "rank": 213, "score": 0.0} +{"id": "id289", "channel": "fts", "rank": 212, "score": 0.0} +{"id": "id290", "channel": "fts", "rank": 211, "score": 0.0} +{"id": "id291", "channel": "fts", "rank": 210, "score": 0.0} +{"id": "id292", "channel": "fts", "rank": 209, "score": 0.0} +{"id": "id293", "channel": "fts", "rank": 208, "score": 0.0} +{"id": "id294", "channel": "fts", "rank": 207, "score": 0.0} +{"id": "id295", "channel": "fts", "rank": 206, "score": 0.0} +{"id": "id296", "channel": "fts", "rank": 205, "score": 0.0} +{"id": "id297", "channel": "fts", "rank": 204, "score": 0.0} +{"id": "id298", "channel": "fts", "rank": 203, "score": 0.0} +{"id": "id299", "channel": "fts", "rank": 202, "score": 0.0} +{"id": "id300", "channel": "fts", "rank": 201, "score": 0.0} +{"id": "id301", "channel": "fts", "rank": 200, "score": 0.0} +{"id": "id302", "channel": "fts", "rank": 199, "score": 0.0} +{"id": "id303", "channel": "fts", "rank": 198, "score": 0.0} +{"id": "id304", "channel": "fts", "rank": 197, "score": 0.0} +{"id": "id305", "channel": "fts", "rank": 196, "score": 0.0} +{"id": "id306", "channel": "fts", "rank": 195, "score": 0.0} +{"id": "id307", "channel": "fts", "rank": 194, "score": 0.0} +{"id": "id308", "channel": "fts", "rank": 193, "score": 0.0} +{"id": "id309", "channel": "fts", "rank": 192, "score": 0.0} +{"id": "id310", "channel": "fts", "rank": 191, "score": 0.0} +{"id": "id311", "channel": "fts", "rank": 190, "score": 0.0} +{"id": "id312", "channel": "fts", "rank": 189, "score": 0.0} +{"id": "id313", "channel": "fts", "rank": 188, "score": 0.0} +{"id": "id314", "channel": "fts", "rank": 187, "score": 0.0} +{"id": "id315", "channel": "fts", "rank": 186, "score": 0.0} +{"id": "id316", "channel": "fts", "rank": 185, "score": 0.0} +{"id": "id317", "channel": "fts", "rank": 184, "score": 0.0} +{"id": "id318", "channel": "fts", "rank": 183, "score": 0.0} +{"id": "id319", "channel": "fts", "rank": 182, "score": 0.0} +{"id": "id320", "channel": "fts", "rank": 181, "score": 0.0} +{"id": "id321", "channel": "fts", "rank": 180, "score": 0.0} +{"id": "id322", "channel": "fts", "rank": 179, "score": 0.0} +{"id": "id323", "channel": "fts", "rank": 178, "score": 0.0} +{"id": "id324", "channel": "fts", "rank": 177, "score": 0.0} +{"id": "id325", "channel": "fts", "rank": 176, "score": 0.0} +{"id": "id326", "channel": "fts", "rank": 175, "score": 0.0} +{"id": "id327", "channel": "fts", "rank": 174, "score": 0.0} +{"id": "id328", "channel": "fts", "rank": 173, "score": 0.0} +{"id": "id329", "channel": "fts", "rank": 172, "score": 0.0} +{"id": "id330", "channel": "fts", "rank": 171, "score": 0.0} +{"id": "id331", "channel": "fts", "rank": 170, "score": 0.0} +{"id": "id332", "channel": "fts", "rank": 169, "score": 0.0} +{"id": "id333", "channel": "fts", "rank": 168, "score": 0.0} +{"id": "id334", "channel": "fts", "rank": 167, "score": 0.0} +{"id": "id335", "channel": "fts", "rank": 166, "score": 0.0} +{"id": "id336", "channel": "fts", "rank": 165, "score": 0.0} +{"id": "id337", "channel": "fts", "rank": 164, "score": 0.0} +{"id": "id338", "channel": "fts", "rank": 163, "score": 0.0} +{"id": "id339", "channel": "fts", "rank": 162, "score": 0.0} +{"id": "id340", "channel": "fts", "rank": 161, "score": 0.0} +{"id": "id341", "channel": "fts", "rank": 160, "score": 0.0} +{"id": "id342", "channel": "fts", "rank": 159, "score": 0.0} +{"id": "id343", "channel": "fts", "rank": 158, "score": 0.0} +{"id": "id344", "channel": "fts", "rank": 157, "score": 0.0} +{"id": "id345", "channel": "fts", "rank": 156, "score": 0.0} +{"id": "id346", "channel": "fts", "rank": 155, "score": 0.0} +{"id": "id347", "channel": "fts", "rank": 154, "score": 0.0} +{"id": "id348", "channel": "fts", "rank": 153, "score": 0.0} +{"id": "id349", "channel": "fts", "rank": 152, "score": 0.0} +{"id": "id350", "channel": "fts", "rank": 151, "score": 0.0} +{"id": "id351", "channel": "fts", "rank": 150, "score": 0.0} +{"id": "id352", "channel": "fts", "rank": 149, "score": 0.0} +{"id": "id353", "channel": "fts", "rank": 148, "score": 0.0} +{"id": "id354", "channel": "fts", "rank": 147, "score": 0.0} +{"id": "id355", "channel": "fts", "rank": 146, "score": 0.0} +{"id": "id356", "channel": "fts", "rank": 145, "score": 0.0} +{"id": "id357", "channel": "fts", "rank": 144, "score": 0.0} +{"id": "id358", "channel": "fts", "rank": 143, "score": 0.0} +{"id": "id359", "channel": "fts", "rank": 142, "score": 0.0} +{"id": "id360", "channel": "fts", "rank": 141, "score": 0.0} +{"id": "id361", "channel": "fts", "rank": 140, "score": 0.0} +{"id": "id362", "channel": "fts", "rank": 139, "score": 0.0} +{"id": "id363", "channel": "fts", "rank": 138, "score": 0.0} +{"id": "id364", "channel": "fts", "rank": 137, "score": 0.0} +{"id": "id365", "channel": "fts", "rank": 136, "score": 0.0} +{"id": "id366", "channel": "fts", "rank": 135, "score": 0.0} +{"id": "id367", "channel": "fts", "rank": 134, "score": 0.0} +{"id": "id368", "channel": "fts", "rank": 133, "score": 0.0} +{"id": "id369", "channel": "fts", "rank": 132, "score": 0.0} +{"id": "id370", "channel": "fts", "rank": 131, "score": 0.0} +{"id": "id371", "channel": "fts", "rank": 130, "score": 0.0} +{"id": "id372", "channel": "fts", "rank": 129, "score": 0.0} +{"id": "id373", "channel": "fts", "rank": 128, "score": 0.0} +{"id": "id374", "channel": "fts", "rank": 127, "score": 0.0} +{"id": "id375", "channel": "fts", "rank": 126, "score": 0.0} +{"id": "id376", "channel": "fts", "rank": 125, "score": 0.0} +{"id": "id377", "channel": "fts", "rank": 124, "score": 0.0} +{"id": "id378", "channel": "fts", "rank": 123, "score": 0.0} +{"id": "id379", "channel": "fts", "rank": 122, "score": 0.0} +{"id": "id380", "channel": "fts", "rank": 121, "score": 0.0} +{"id": "id381", "channel": "fts", "rank": 120, "score": 0.0} +{"id": "id382", "channel": "fts", "rank": 119, "score": 0.0} +{"id": "id383", "channel": "fts", "rank": 118, "score": 0.0} +{"id": "id384", "channel": "fts", "rank": 117, "score": 0.0} +{"id": "id385", "channel": "fts", "rank": 116, "score": 0.0} +{"id": "id386", "channel": "fts", "rank": 115, "score": 0.0} +{"id": "id387", "channel": "fts", "rank": 114, "score": 0.0} +{"id": "id388", "channel": "fts", "rank": 113, "score": 0.0} +{"id": "id389", "channel": "fts", "rank": 112, "score": 0.0} +{"id": "id390", "channel": "fts", "rank": 111, "score": 0.0} +{"id": "id391", "channel": "fts", "rank": 110, "score": 0.0} +{"id": "id392", "channel": "fts", "rank": 109, "score": 0.0} +{"id": "id393", "channel": "fts", "rank": 108, "score": 0.0} +{"id": "id394", "channel": "fts", "rank": 107, "score": 0.0} +{"id": "id395", "channel": "fts", "rank": 106, "score": 0.0} +{"id": "id396", "channel": "fts", "rank": 105, "score": 0.0} +{"id": "id397", "channel": "fts", "rank": 104, "score": 0.0} +{"id": "id398", "channel": "fts", "rank": 103, "score": 0.0} +{"id": "id399", "channel": "fts", "rank": 102, "score": 0.0} +{"id": "id400", "channel": "fts", "rank": 101, "score": 0.0} +{"id": "id401", "channel": "fts", "rank": 100, "score": 0.0} +{"id": "id402", "channel": "fts", "rank": 99, "score": 0.0} +{"id": "id403", "channel": "fts", "rank": 98, "score": 0.0} +{"id": "id404", "channel": "fts", "rank": 97, "score": 0.0} +{"id": "id405", "channel": "fts", "rank": 96, "score": 0.0} +{"id": "id406", "channel": "fts", "rank": 95, "score": 0.0} +{"id": "id407", "channel": "fts", "rank": 94, "score": 0.0} +{"id": "id408", "channel": "fts", "rank": 93, "score": 0.0} +{"id": "id409", "channel": "fts", "rank": 92, "score": 0.0} +{"id": "id410", "channel": "fts", "rank": 91, "score": 0.0} +{"id": "id411", "channel": "fts", "rank": 90, "score": 0.0} +{"id": "id412", "channel": "fts", "rank": 89, "score": 0.0} +{"id": "id413", "channel": "fts", "rank": 88, "score": 0.0} +{"id": "id414", "channel": "fts", "rank": 87, "score": 0.0} +{"id": "id415", "channel": "fts", "rank": 86, "score": 0.0} +{"id": "id416", "channel": "fts", "rank": 85, "score": 0.0} +{"id": "id417", "channel": "fts", "rank": 84, "score": 0.0} +{"id": "id418", "channel": "fts", "rank": 83, "score": 0.0} +{"id": "id419", "channel": "fts", "rank": 82, "score": 0.0} +{"id": "id420", "channel": "fts", "rank": 81, "score": 0.0} +{"id": "id421", "channel": "fts", "rank": 80, "score": 0.0} +{"id": "id422", "channel": "fts", "rank": 79, "score": 0.0} +{"id": "id423", "channel": "fts", "rank": 78, "score": 0.0} +{"id": "id424", "channel": "fts", "rank": 77, "score": 0.0} +{"id": "id425", "channel": "fts", "rank": 76, "score": 0.0} +{"id": "id426", "channel": "fts", "rank": 75, "score": 0.0} +{"id": "id427", "channel": "fts", "rank": 74, "score": 0.0} +{"id": "id428", "channel": "fts", "rank": 73, "score": 0.0} +{"id": "id429", "channel": "fts", "rank": 72, "score": 0.0} +{"id": "id430", "channel": "fts", "rank": 71, "score": 0.0} +{"id": "id431", "channel": "fts", "rank": 70, "score": 0.0} +{"id": "id432", "channel": "fts", "rank": 69, "score": 0.0} +{"id": "id433", "channel": "fts", "rank": 68, "score": 0.0} +{"id": "id434", "channel": "fts", "rank": 67, "score": 0.0} +{"id": "id435", "channel": "fts", "rank": 66, "score": 0.0} +{"id": "id436", "channel": "fts", "rank": 65, "score": 0.0} +{"id": "id437", "channel": "fts", "rank": 64, "score": 0.0} +{"id": "id438", "channel": "fts", "rank": 63, "score": 0.0} +{"id": "id439", "channel": "fts", "rank": 62, "score": 0.0} +{"id": "id440", "channel": "fts", "rank": 61, "score": 0.0} +{"id": "id441", "channel": "fts", "rank": 60, "score": 0.0} +{"id": "id442", "channel": "fts", "rank": 59, "score": 0.0} +{"id": "id443", "channel": "fts", "rank": 58, "score": 0.0} +{"id": "id444", "channel": "fts", "rank": 57, "score": 0.0} +{"id": "id445", "channel": "fts", "rank": 56, "score": 0.0} +{"id": "id446", "channel": "fts", "rank": 55, "score": 0.0} +{"id": "id447", "channel": "fts", "rank": 54, "score": 0.0} +{"id": "id448", "channel": "fts", "rank": 53, "score": 0.0} +{"id": "id449", "channel": "fts", "rank": 52, "score": 0.0} +{"id": "id450", "channel": "fts", "rank": 51, "score": 0.0} +{"id": "id451", "channel": "fts", "rank": 50, "score": 0.0} +{"id": "id452", "channel": "fts", "rank": 49, "score": 0.0} +{"id": "id453", "channel": "fts", "rank": 48, "score": 0.0} +{"id": "id454", "channel": "fts", "rank": 47, "score": 0.0} +{"id": "id455", "channel": "fts", "rank": 46, "score": 0.0} +{"id": "id456", "channel": "fts", "rank": 45, "score": 0.0} +{"id": "id457", "channel": "fts", "rank": 44, "score": 0.0} +{"id": "id458", "channel": "fts", "rank": 43, "score": 0.0} +{"id": "id459", "channel": "fts", "rank": 42, "score": 0.0} +{"id": "id460", "channel": "fts", "rank": 41, "score": 0.0} +{"id": "id461", "channel": "fts", "rank": 40, "score": 0.0} +{"id": "id462", "channel": "fts", "rank": 39, "score": 0.0} +{"id": "id463", "channel": "fts", "rank": 38, "score": 0.0} +{"id": "id464", "channel": "fts", "rank": 37, "score": 0.0} +{"id": "id465", "channel": "fts", "rank": 36, "score": 0.0} +{"id": "id466", "channel": "fts", "rank": 35, "score": 0.0} +{"id": "id467", "channel": "fts", "rank": 34, "score": 0.0} +{"id": "id468", "channel": "fts", "rank": 33, "score": 0.0} +{"id": "id469", "channel": "fts", "rank": 32, "score": 0.0} +{"id": "id470", "channel": "fts", "rank": 31, "score": 0.0} +{"id": "id471", "channel": "fts", "rank": 30, "score": 0.0} +{"id": "id472", "channel": "fts", "rank": 29, "score": 0.0} +{"id": "id473", "channel": "fts", "rank": 28, "score": 0.0} +{"id": "id474", "channel": "fts", "rank": 27, "score": 0.0} +{"id": "id475", "channel": "fts", "rank": 26, "score": 0.0} +{"id": "id476", "channel": "fts", "rank": 25, "score": 0.0} +{"id": "id477", "channel": "fts", "rank": 24, "score": 0.0} +{"id": "id478", "channel": "fts", "rank": 23, "score": 0.0} +{"id": "id479", "channel": "fts", "rank": 22, "score": 0.0} +{"id": "id480", "channel": "fts", "rank": 21, "score": 0.0} +{"id": "id481", "channel": "fts", "rank": 20, "score": 0.0} +{"id": "id482", "channel": "fts", "rank": 19, "score": 0.0} +{"id": "id483", "channel": "fts", "rank": 18, "score": 0.0} +{"id": "id484", "channel": "fts", "rank": 17, "score": 0.0} +{"id": "id485", "channel": "fts", "rank": 16, "score": 0.0} +{"id": "id486", "channel": "fts", "rank": 15, "score": 0.0} +{"id": "id487", "channel": "fts", "rank": 14, "score": 0.0} +{"id": "id488", "channel": "fts", "rank": 13, "score": 0.0} +{"id": "id489", "channel": "fts", "rank": 12, "score": 0.0} +{"id": "id490", "channel": "fts", "rank": 11, "score": 0.0} +{"id": "id491", "channel": "fts", "rank": 10, "score": 0.0} +{"id": "id492", "channel": "fts", "rank": 9, "score": 0.0} +{"id": "id493", "channel": "fts", "rank": 8, "score": 0.0} +{"id": "id494", "channel": "fts", "rank": 7, "score": 0.0} +{"id": "id495", "channel": "fts", "rank": 6, "score": 0.0} +{"id": "id496", "channel": "fts", "rank": 5, "score": 0.0} +{"id": "id497", "channel": "fts", "rank": 4, "score": 0.0} +{"id": "id498", "channel": "fts", "rank": 3, "score": 0.0} +{"id": "id499", "channel": "fts", "rank": 2, "score": 0.0} +{"id": "id500", "channel": "fts", "rank": 1, "score": 0.0} diff --git a/parity/cases/rrf/interleaved.ndjson b/parity/cases/rrf/interleaved.ndjson new file mode 100644 index 0000000..9bae4bb --- /dev/null +++ b/parity/cases/rrf/interleaved.ndjson @@ -0,0 +1,10 @@ +{"id": "id5", "channel": "vec", "rank": 5, "score": 0.0} +{"id": "id1", "channel": "fts", "rank": 5, "score": 0.0} +{"id": "id1", "channel": "vec", "rank": 1, "score": 0.0} +{"id": "id5", "channel": "fts", "rank": 1, "score": 0.0} +{"id": "id3", "channel": "vec", "rank": 3, "score": 0.0} +{"id": "id3", "channel": "fts", "rank": 3, "score": 0.0} +{"id": "id2", "channel": "vec", "rank": 2, "score": 0.0} +{"id": "id4", "channel": "fts", "rank": 2, "score": 0.0} +{"id": "id4", "channel": "vec", "rank": 4, "score": 0.0} +{"id": "id2", "channel": "fts", "rank": 4, "score": 0.0} diff --git a/parity/cases/rrf/one-channel.ndjson b/parity/cases/rrf/one-channel.ndjson new file mode 100644 index 0000000..576ae97 --- /dev/null +++ b/parity/cases/rrf/one-channel.ndjson @@ -0,0 +1,20 @@ +{"id": "id1", "channel": "vec", "rank": 1, "score": 1.0} +{"id": "id2", "channel": "vec", "rank": 2, "score": 0.5} +{"id": "id3", "channel": "vec", "rank": 3, "score": 0.3333333333333333} +{"id": "id4", "channel": "vec", "rank": 4, "score": 0.25} +{"id": "id5", "channel": "vec", "rank": 5, "score": 0.2} +{"id": "id6", "channel": "vec", "rank": 6, "score": 0.16666666666666666} +{"id": "id7", "channel": "vec", "rank": 7, "score": 0.14285714285714285} +{"id": "id8", "channel": "vec", "rank": 8, "score": 0.125} +{"id": "id9", "channel": "vec", "rank": 9, "score": 0.1111111111111111} +{"id": "id10", "channel": "vec", "rank": 10, "score": 0.1} +{"id": "id11", "channel": "vec", "rank": 11, "score": 0.09090909090909091} +{"id": "id12", "channel": "vec", "rank": 12, "score": 0.08333333333333333} +{"id": "id13", "channel": "vec", "rank": 13, "score": 0.07692307692307693} +{"id": "id14", "channel": "vec", "rank": 14, "score": 0.07142857142857142} +{"id": "id15", "channel": "vec", "rank": 15, "score": 0.06666666666666667} +{"id": "id16", "channel": "vec", "rank": 16, "score": 0.0625} +{"id": "id17", "channel": "vec", "rank": 17, "score": 0.058823529411764705} +{"id": "id18", "channel": "vec", "rank": 18, "score": 0.05555555555555555} +{"id": "id19", "channel": "vec", "rank": 19, "score": 0.05263157894736842} +{"id": "id20", "channel": "vec", "rank": 20, "score": 0.05} diff --git a/parity/cases/rrf/repeat.ndjson b/parity/cases/rrf/repeat.ndjson new file mode 100644 index 0000000..af776ba --- /dev/null +++ b/parity/cases/rrf/repeat.ndjson @@ -0,0 +1,3 @@ +{"id": "a", "channel": "vec", "rank": 9, "score": 0.0} +{"id": "a", "channel": "vec", "rank": 1, "score": 0.0} +{"id": "b", "channel": "vec", "rank": 2, "score": 0.0} diff --git a/parity/cases/rrf/single.ndjson b/parity/cases/rrf/single.ndjson new file mode 100644 index 0000000..cce0e06 --- /dev/null +++ b/parity/cases/rrf/single.ndjson @@ -0,0 +1 @@ +{"id": "only", "channel": "meta", "rank": 1, "score": 0.0} diff --git a/parity/cases/rrf/three-channels.ndjson b/parity/cases/rrf/three-channels.ndjson new file mode 100644 index 0000000..bbc887c --- /dev/null +++ b/parity/cases/rrf/three-channels.ndjson @@ -0,0 +1,16 @@ +{"id": "a", "channel": "vec", "rank": 1, "score": 1.0} +{"id": "b", "channel": "vec", "rank": 2, "score": 0.5} +{"id": "c", "channel": "vec", "rank": 3, "score": 0.3333333333333333} +{"id": "d", "channel": "vec", "rank": 4, "score": 0.25} +{"id": "e", "channel": "vec", "rank": 5, "score": 0.2} +{"id": "f", "channel": "vec", "rank": 6, "score": 0.16666666666666666} +{"id": "g", "channel": "vec", "rank": 7, "score": 0.14285714285714285} +{"id": "h", "channel": "vec", "rank": 8, "score": 0.125} +{"id": "c", "channel": "fts", "rank": 1, "score": 9.0} +{"id": "a", "channel": "fts", "rank": 2, "score": 8.0} +{"id": "x", "channel": "fts", "rank": 3, "score": 7.0} +{"id": "y", "channel": "fts", "rank": 4, "score": 6.0} +{"id": "b", "channel": "fts", "rank": 5, "score": 5.0} +{"id": "z", "channel": "fts", "rank": 6, "score": 4.0} +{"id": "y", "channel": "meta", "rank": 1, "score": 0.0} +{"id": "a", "channel": "meta", "rank": 2, "score": 0.0} diff --git a/parity/golden/rrf/all-tied.ndjson b/parity/golden/rrf/all-tied.ndjson new file mode 100644 index 0000000..61f0008 --- /dev/null +++ b/parity/golden/rrf/all-tied.ndjson @@ -0,0 +1,10 @@ +{"id": "id0", "rank": 1, "score": 0.015384615384615385} +{"id": "id1", "rank": 2, "score": 0.015384615384615385} +{"id": "id2", "rank": 3, "score": 0.015384615384615385} +{"id": "id3", "rank": 4, "score": 0.015384615384615385} +{"id": "id4", "rank": 5, "score": 0.015384615384615385} +{"id": "id5", "rank": 6, "score": 0.015384615384615385} +{"id": "id6", "rank": 7, "score": 0.015384615384615385} +{"id": "id7", "rank": 8, "score": 0.015384615384615385} +{"id": "id8", "rank": 9, "score": 0.015384615384615385} +{"id": "id9", "rank": 10, "score": 0.015384615384615385} diff --git a/parity/golden/rrf/deep.ndjson b/parity/golden/rrf/deep.ndjson new file mode 100644 index 0000000..a401db8 --- /dev/null +++ b/parity/golden/rrf/deep.ndjson @@ -0,0 +1,500 @@ +{"id": "id1", "rank": 1, "score": 0.018179156908665107} +{"id": "id500", "rank": 2, "score": 0.018179156908665107} +{"id": "id2", "rank": 3, "score": 0.01791794102371747} +{"id": "id499", "rank": 4, "score": 0.01791794102371747} +{"id": "id3", "rank": 5, "score": 0.017665130568356373} +{"id": "id498", "rank": 6, "score": 0.017665130568356373} +{"id": "id4", "rank": 7, "score": 0.01742033213644524} +{"id": "id497", "rank": 8, "score": 0.01742033213644524} +{"id": "id5", "rank": 9, "score": 0.01718317653569452} +{"id": "id496", "rank": 10, "score": 0.01718317653569452} +{"id": "id6", "rank": 11, "score": 0.016953316953316953} +{"id": "id495", "rank": 12, "score": 0.016953316953316953} +{"id": "id7", "rank": 13, "score": 0.016730427285952906} +{"id": "id494", "rank": 14, "score": 0.016730427285952906} +{"id": "id8", "rank": 15, "score": 0.016514200616955643} +{"id": "id493", "rank": 16, "score": 0.016514200616955643} +{"id": "id9", "rank": 17, "score": 0.016304347826086956} +{"id": "id492", "rank": 18, "score": 0.016304347826086956} +{"id": "id10", "rank": 19, "score": 0.016100596318382163} +{"id": "id491", "rank": 20, "score": 0.016100596318382163} +{"id": "id11", "rank": 21, "score": 0.01590268886043534} +{"id": "id490", "rank": 22, "score": 0.01590268886043534} +{"id": "id12", "rank": 23, "score": 0.015710382513661202} +{"id": "id489", "rank": 24, "score": 0.015710382513661202} +{"id": "id13", "rank": 25, "score": 0.015523447655234475} +{"id": "id488", "rank": 26, "score": 0.015523447655234475} +{"id": "id14", "rank": 27, "score": 0.015341667078412965} +{"id": "id487", "rank": 28, "score": 0.015341667078412965} +{"id": "id15", "rank": 29, "score": 0.015164835164835166} +{"id": "id486", "rank": 30, "score": 0.015164835164835166} +{"id": "id16", "rank": 31, "score": 0.014992757122163206} +{"id": "id485", "rank": 32, "score": 0.014992757122163206} +{"id": "id17", "rank": 33, "score": 0.014825248281130635} +{"id": "id484", "rank": 34, "score": 0.014825248281130635} +{"id": "id18", "rank": 35, "score": 0.014662133446663833} +{"id": "id483", "rank": 36, "score": 0.014662133446663833} +{"id": "id19", "rank": 37, "score": 0.014503246298285767} +{"id": "id482", "rank": 38, "score": 0.014503246298285767} +{"id": "id20", "rank": 39, "score": 0.014348428835489833} +{"id": "id481", "rank": 40, "score": 0.014348428835489833} +{"id": "id21", "rank": 41, "score": 0.01419753086419753} +{"id": "id480", "rank": 42, "score": 0.01419753086419753} +{"id": "id22", "rank": 43, "score": 0.014050409520792796} +{"id": "id479", "rank": 44, "score": 0.014050409520792796} +{"id": "id23", "rank": 45, "score": 0.013906928830563891} +{"id": "id478", "rank": 46, "score": 0.013906928830563891} +{"id": "id24", "rank": 47, "score": 0.013766959297685554} +{"id": "id477", "rank": 48, "score": 0.013766959297685554} +{"id": "id25", "rank": 49, "score": 0.013630377524143986} +{"id": "id476", "rank": 50, "score": 0.013630377524143986} +{"id": "id26", "rank": 51, "score": 0.013497065855248859} +{"id": "id475", "rank": 52, "score": 0.013497065855248859} +{"id": "id27", "rank": 53, "score": 0.01336691204959318} +{"id": "id474", "rank": 54, "score": 0.01336691204959318} +{"id": "id28", "rank": 55, "score": 0.01323980897151629} +{"id": "id473", "rank": 56, "score": 0.01323980897151629} +{"id": "id29", "rank": 57, "score": 0.013115654304300076} +{"id": "id472", "rank": 58, "score": 0.013115654304300076} +{"id": "id30", "rank": 59, "score": 0.012994350282485877} +{"id": "id471", "rank": 60, "score": 0.012994350282485877} +{"id": "id31", "rank": 61, "score": 0.012875803441841178} +{"id": "id470", "rank": 62, "score": 0.012875803441841178} +{"id": "id32", "rank": 63, "score": 0.01275992438563327} +{"id": "id469", "rank": 64, "score": 0.01275992438563327} +{"id": "id33", "rank": 65, "score": 0.012646627565982406} +{"id": "id468", "rank": 66, "score": 0.012646627565982406} +{"id": "id34", "rank": 67, "score": 0.012535831079171545} +{"id": "id467", "rank": 68, "score": 0.012535831079171545} +{"id": "id35", "rank": 69, "score": 0.012427456473884331} +{"id": "id466", "rank": 70, "score": 0.012427456473884331} +{"id": "id36", "rank": 71, "score": 0.01232142857142857} +{"id": "id465", "rank": 72, "score": 0.01232142857142857} +{"id": "id37", "rank": 73, "score": 0.012217675297080349} +{"id": "id464", "rank": 74, "score": 0.012217675297080349} +{"id": "id38", "rank": 75, "score": 0.0121161275217544} +{"id": "id463", "rank": 76, "score": 0.0121161275217544} +{"id": "id39", "rank": 77, "score": 0.012016718913270639} +{"id": "id462", "rank": 78, "score": 0.012016718913270639} +{"id": "id40", "rank": 79, "score": 0.011919385796545105} +{"id": "id461", "rank": 80, "score": 0.011919385796545105} +{"id": "id41", "rank": 81, "score": 0.011824067022086824} +{"id": "id460", "rank": 82, "score": 0.011824067022086824} +{"id": "id42", "rank": 83, "score": 0.011730703842230534} +{"id": "id459", "rank": 84, "score": 0.011730703842230534} +{"id": "id43", "rank": 85, "score": 0.0116392397945796} +{"id": "id458", "rank": 86, "score": 0.0116392397945796} +{"id": "id44", "rank": 87, "score": 0.011549620592173784} +{"id": "id457", "rank": 88, "score": 0.011549620592173784} +{"id": "id45", "rank": 89, "score": 0.011461794019933556} +{"id": "id456", "rank": 90, "score": 0.011461794019933556} +{"id": "id46", "rank": 91, "score": 0.011375709836966477} +{"id": "id455", "rank": 92, "score": 0.011375709836966477} +{"id": "id47", "rank": 93, "score": 0.011291319684352158} +{"id": "id454", "rank": 94, "score": 0.011291319684352158} +{"id": "id48", "rank": 95, "score": 0.011208576998050682} +{"id": "id453", "rank": 96, "score": 0.011208576998050682} +{"id": "id49", "rank": 97, "score": 0.011127436926605505} +{"id": "id452", "rank": 98, "score": 0.011127436926605505} +{"id": "id50", "rank": 99, "score": 0.011047856253335705} +{"id": "id451", "rank": 100, "score": 0.011047856253335705} +{"id": "id51", "rank": 101, "score": 0.010969793322734499} +{"id": "id450", "rank": 102, "score": 0.010969793322734499} +{"id": "id52", "rank": 103, "score": 0.010893207970811114} +{"id": "id449", "rank": 104, "score": 0.010893207970811114} +{"id": "id53", "rank": 105, "score": 0.010818061459131768} +{"id": "id448", "rank": 106, "score": 0.010818061459131768} +{"id": "id54", "rank": 107, "score": 0.010744316412332606} +{"id": "id447", "rank": 108, "score": 0.010744316412332606} +{"id": "id55", "rank": 109, "score": 0.010671936758893281} +{"id": "id446", "rank": 110, "score": 0.010671936758893281} +{"id": "id56", "rank": 111, "score": 0.010600887674974394} +{"id": "id445", "rank": 112, "score": 0.010600887674974394} +{"id": "id57", "rank": 113, "score": 0.010531135531135532} +{"id": "id444", "rank": 114, "score": 0.010531135531135532} +{"id": "id58", "rank": 115, "score": 0.01046264784176298} +{"id": "id443", "rank": 116, "score": 0.01046264784176298} +{"id": "id59", "rank": 117, "score": 0.010395393217047775} +{"id": "id442", "rank": 118, "score": 0.010395393217047775} +{"id": "id60", "rank": 119, "score": 0.010329341317365268} +{"id": "id441", "rank": 120, "score": 0.010329341317365268} +{"id": "id61", "rank": 121, "score": 0.010264462809917356} +{"id": "id440", "rank": 122, "score": 0.010264462809917356} +{"id": "id62", "rank": 123, "score": 0.010200729327507475} +{"id": "id439", "rank": 124, "score": 0.010200729327507475} +{"id": "id63", "rank": 125, "score": 0.010138113429327064} +{"id": "id438", "rank": 126, "score": 0.010138113429327064} +{"id": "id64", "rank": 127, "score": 0.010076588563639903} +{"id": "id437", "rank": 128, "score": 0.010076588563639903} +{"id": "id65", "rank": 129, "score": 0.010016129032258065} +{"id": "id436", "rank": 130, "score": 0.010016129032258065} +{"id": "id66", "rank": 131, "score": 0.009956709956709955} +{"id": "id435", "rank": 132, "score": 0.009956709956709955} +{"id": "id67", "rank": 133, "score": 0.009898307246007204} +{"id": "id434", "rank": 134, "score": 0.009898307246007204} +{"id": "id68", "rank": 135, "score": 0.00984089756592292} +{"id": "id433", "rank": 136, "score": 0.00984089756592292} +{"id": "id69", "rank": 137, "score": 0.009784458309699377} +{"id": "id432", "rank": 138, "score": 0.009784458309699377} +{"id": "id70", "rank": 139, "score": 0.0097289675701081} +{"id": "id431", "rank": 140, "score": 0.0097289675701081} +{"id": "id71", "rank": 141, "score": 0.009674404112790153} +{"id": "id430", "rank": 142, "score": 0.009674404112790153} +{"id": "id72", "rank": 143, "score": 0.0096207473508087} +{"id": "id429", "rank": 144, "score": 0.0096207473508087} +{"id": "id73", "rank": 145, "score": 0.009567977320350056} +{"id": "id428", "rank": 146, "score": 0.009567977320350056} +{"id": "id74", "rank": 147, "score": 0.009516074657513255} +{"id": "id427", "rank": 148, "score": 0.009516074657513255} +{"id": "id75", "rank": 149, "score": 0.009465020576131689} +{"id": "id426", "rank": 150, "score": 0.009465020576131689} +{"id": "id76", "rank": 151, "score": 0.00941479684657368} +{"id": "id425", "rank": 152, "score": 0.00941479684657368} +{"id": "id77", "rank": 153, "score": 0.00936538577547204} +{"id": "id424", "rank": 154, "score": 0.00936538577547204} +{"id": "id78", "rank": 155, "score": 0.009316770186335404} +{"id": "id423", "rank": 156, "score": 0.009316770186335404} +{"id": "id79", "rank": 157, "score": 0.009268933400997045} +{"id": "id422", "rank": 158, "score": 0.009268933400997045} +{"id": "id80", "rank": 159, "score": 0.009221859221859221} +{"id": "id421", "rank": 160, "score": 0.009221859221859221} +{"id": "id81", "rank": 161, "score": 0.009175531914893616} +{"id": "id420", "rank": 162, "score": 0.009175531914893616} +{"id": "id82", "rank": 163, "score": 0.009129936193360581} +{"id": "id419", "rank": 164, "score": 0.009129936193360581} +{"id": "id83", "rank": 165, "score": 0.009085057202212013} +{"id": "id418", "rank": 166, "score": 0.009085057202212013} +{"id": "id84", "rank": 167, "score": 0.009040880503144654} +{"id": "id417", "rank": 168, "score": 0.009040880503144654} +{"id": "id85", "rank": 169, "score": 0.008997392060272384} +{"id": "id416", "rank": 170, "score": 0.008997392060272384} +{"id": "id86", "rank": 171, "score": 0.008954578226387887} +{"id": "id415", "rank": 172, "score": 0.008954578226387887} +{"id": "id87", "rank": 173, "score": 0.008912425729785584} +{"id": "id414", "rank": 174, "score": 0.008912425729785584} +{"id": "id88", "rank": 175, "score": 0.008870921661619336} +{"id": "id413", "rank": 176, "score": 0.008870921661619336} +{"id": "id89", "rank": 177, "score": 0.008830053463769765} +{"id": "id412", "rank": 178, "score": 0.008830053463769765} +{"id": "id90", "rank": 179, "score": 0.008789808917197453} +{"id": "id411", "rank": 180, "score": 0.008789808917197453} +{"id": "id91", "rank": 181, "score": 0.008750176130759476} +{"id": "id410", "rank": 182, "score": 0.008750176130759476} +{"id": "id92", "rank": 183, "score": 0.00871114353046796} +{"id": "id409", "rank": 184, "score": 0.00871114353046796} +{"id": "id93", "rank": 185, "score": 0.008672699849170438} +{"id": "id408", "rank": 186, "score": 0.008672699849170438} +{"id": "id94", "rank": 187, "score": 0.008634834116632833} +{"id": "id407", "rank": 188, "score": 0.008634834116632833} +{"id": "id95", "rank": 189, "score": 0.008597535650006923} +{"id": "id406", "rank": 190, "score": 0.008597535650006923} +{"id": "id96", "rank": 191, "score": 0.008560794044665011} +{"id": "id405", "rank": 192, "score": 0.008560794044665011} +{"id": "id97", "rank": 193, "score": 0.00852459916538546} +{"id": "id404", "rank": 194, "score": 0.00852459916538546} +{"id": "id98", "rank": 195, "score": 0.008488941137873527} +{"id": "id403", "rank": 196, "score": 0.008488941137873527} +{"id": "id99", "rank": 197, "score": 0.008453810340602793} +{"id": "id402", "rank": 198, "score": 0.008453810340602793} +{"id": "id100", "rank": 199, "score": 0.008419197396963124} +{"id": "id401", "rank": 200, "score": 0.008419197396963124} +{"id": "id101", "rank": 201, "score": 0.008385093167701863} +{"id": "id400", "rank": 202, "score": 0.008385093167701863} +{"id": "id102", "rank": 203, "score": 0.008351488743645606} +{"id": "id399", "rank": 204, "score": 0.008351488743645606} +{"id": "id103", "rank": 205, "score": 0.008318375438690493} +{"id": "id398", "rank": 206, "score": 0.008318375438690493} +{"id": "id104", "rank": 207, "score": 0.008285744783049581} +{"id": "id397", "rank": 208, "score": 0.008285744783049581} +{"id": "id105", "rank": 209, "score": 0.008253588516746412} +{"id": "id396", "rank": 210, "score": 0.008253588516746412} +{"id": "id106", "rank": 211, "score": 0.008221898583344368} +{"id": "id395", "rank": 212, "score": 0.008221898583344368} +{"id": "id107", "rank": 213, "score": 0.008190667123901976} +{"id": "id394", "rank": 214, "score": 0.008190667123901976} +{"id": "id108", "rank": 215, "score": 0.00815988647114475} +{"id": "id393", "rank": 216, "score": 0.00815988647114475} +{"id": "id109", "rank": 217, "score": 0.008129549143844582} +{"id": "id392", "rank": 218, "score": 0.008129549143844582} +{"id": "id110", "rank": 219, "score": 0.008099647841398199} +{"id": "id391", "rank": 220, "score": 0.008099647841398199} +{"id": "id111", "rank": 221, "score": 0.008070175438596491} +{"id": "id390", "rank": 222, "score": 0.008070175438596491} +{"id": "id112", "rank": 223, "score": 0.008041124980576993} +{"id": "id389", "rank": 224, "score": 0.008041124980576993} +{"id": "id113", "rank": 225, "score": 0.008012489677952105} +{"id": "id388", "rank": 226, "score": 0.008012489677952105} +{"id": "id114", "rank": 227, "score": 0.007984262902105993} +{"id": "id387", "rank": 228, "score": 0.007984262902105993} +{"id": "id115", "rank": 229, "score": 0.007956438180653428} +{"id": "id386", "rank": 230, "score": 0.007956438180653428} +{"id": "id116", "rank": 231, "score": 0.007929009193054138} +{"id": "id385", "rank": 232, "score": 0.007929009193054138} +{"id": "id117", "rank": 233, "score": 0.007901969766376546} +{"id": "id384", "rank": 234, "score": 0.007901969766376546} +{"id": "id118", "rank": 235, "score": 0.007875313871205011} +{"id": "id383", "rank": 236, "score": 0.007875313871205011} +{"id": "id119", "rank": 237, "score": 0.007849035617684977} +{"id": "id382", "rank": 238, "score": 0.007849035617684977} +{"id": "id120", "rank": 239, "score": 0.007823129251700681} +{"id": "id381", "rank": 240, "score": 0.007823129251700681} +{"id": "id121", "rank": 241, "score": 0.007797589151180311} +{"id": "id380", "rank": 242, "score": 0.007797589151180311} +{"id": "id122", "rank": 243, "score": 0.0077724098225237185} +{"id": "id379", "rank": 244, "score": 0.0077724098225237185} +{"id": "id123", "rank": 245, "score": 0.00774758589714799} +{"id": "id378", "rank": 246, "score": 0.00774758589714799} +{"id": "id124", "rank": 247, "score": 0.0077231121281464525} +{"id": "id377", "rank": 248, "score": 0.0077231121281464525} +{"id": "id125", "rank": 249, "score": 0.007698983387056782} +{"id": "id376", "rank": 250, "score": 0.007698983387056782} +{"id": "id126", "rank": 251, "score": 0.0076751946607341495} +{"id": "id375", "rank": 252, "score": 0.0076751946607341495} +{"id": "id127", "rank": 253, "score": 0.007651741048325488} +{"id": "id374", "rank": 254, "score": 0.007651741048325488} +{"id": "id128", "rank": 255, "score": 0.007628617758341113} +{"id": "id373", "rank": 256, "score": 0.007628617758341113} +{"id": "id129", "rank": 257, "score": 0.007605820105820105} +{"id": "id372", "rank": 258, "score": 0.007605820105820105} +{"id": "id130", "rank": 259, "score": 0.00758334350958603} +{"id": "id371", "rank": 260, "score": 0.00758334350958603} +{"id": "id131", "rank": 261, "score": 0.007561183489589675} +{"id": "id370", "rank": 262, "score": 0.007561183489589675} +{"id": "id132", "rank": 263, "score": 0.007539335664335664} +{"id": "id369", "rank": 264, "score": 0.007539335664335664} +{"id": "id133", "rank": 265, "score": 0.007517795748389908} +{"id": "id368", "rank": 266, "score": 0.007517795748389908} +{"id": "id134", "rank": 267, "score": 0.007496559549964992} +{"id": "id367", "rank": 268, "score": 0.007496559549964992} +{"id": "id135", "rank": 269, "score": 0.007475622968580715} +{"id": "id366", "rank": 270, "score": 0.007475622968580715} +{"id": "id136", "rank": 271, "score": 0.007454981992797118} +{"id": "id365", "rank": 272, "score": 0.007454981992797118} +{"id": "id137", "rank": 273, "score": 0.00743463269801743} +{"id": "id364", "rank": 274, "score": 0.00743463269801743} +{"id": "id138", "rank": 275, "score": 0.007414571244358479} +{"id": "id363", "rank": 276, "score": 0.007414571244358479} +{"id": "id139", "rank": 277, "score": 0.007394793874586201} +{"id": "id362", "rank": 278, "score": 0.007394793874586201} +{"id": "id140", "rank": 279, "score": 0.0073752969121140145} +{"id": "id361", "rank": 280, "score": 0.0073752969121140145} +{"id": "id141", "rank": 281, "score": 0.007356076759061834} +{"id": "id360", "rank": 282, "score": 0.007356076759061834} +{"id": "id142", "rank": 283, "score": 0.007337129894373686} +{"id": "id359", "rank": 284, "score": 0.007337129894373686} +{"id": "id143", "rank": 285, "score": 0.0073184528719918915} +{"id": "id358", "rank": 286, "score": 0.0073184528719918915} +{"id": "id144", "rank": 287, "score": 0.0073000423190859075} +{"id": "id357", "rank": 288, "score": 0.0073000423190859075} +{"id": "id145", "rank": 289, "score": 0.007281894934333959} +{"id": "id356", "rank": 290, "score": 0.007281894934333959} +{"id": "id146", "rank": 291, "score": 0.007264007486255702} +{"id": "id355", "rank": 292, "score": 0.007264007486255702} +{"id": "id147", "rank": 293, "score": 0.007246376811594202} +{"id": "id354", "rank": 294, "score": 0.007246376811594202} +{"id": "id148", "rank": 295, "score": 0.007228999813745577} +{"id": "id353", "rank": 296, "score": 0.007228999813745577} +{"id": "id149", "rank": 297, "score": 0.007211873461234728} +{"id": "id352", "rank": 298, "score": 0.007211873461234728} +{"id": "id150", "rank": 299, "score": 0.007194994786235663} +{"id": "id351", "rank": 300, "score": 0.007194994786235663} +{"id": "id151", "rank": 301, "score": 0.0071783608831348985} +{"id": "id350", "rank": 302, "score": 0.0071783608831348985} +{"id": "id152", "rank": 303, "score": 0.007161968907136596} +{"id": "id349", "rank": 304, "score": 0.007161968907136596} +{"id": "id153", "rank": 305, "score": 0.0071458160729080365} +{"id": "id348", "rank": 306, "score": 0.0071458160729080365} +{"id": "id154", "rank": 307, "score": 0.007129899653264139} +{"id": "id347", "rank": 308, "score": 0.007129899653264139} +{"id": "id155", "rank": 309, "score": 0.007114216977889793} +{"id": "id346", "rank": 310, "score": 0.007114216977889793} +{"id": "id156", "rank": 311, "score": 0.007098765432098765} +{"id": "id345", "rank": 312, "score": 0.007098765432098765} +{"id": "id157", "rank": 313, "score": 0.007083542455628051} +{"id": "id344", "rank": 314, "score": 0.007083542455628051} +{"id": "id158", "rank": 315, "score": 0.007068545541466524} +{"id": "id343", "rank": 316, "score": 0.007068545541466524} +{"id": "id159", "rank": 317, "score": 0.0070537722347168265} +{"id": "id342", "rank": 318, "score": 0.0070537722347168265} +{"id": "id160", "rank": 319, "score": 0.007039220131489458} +{"id": "id341", "rank": 320, "score": 0.007039220131489458} +{"id": "id161", "rank": 321, "score": 0.007024886877828054} +{"id": "id340", "rank": 322, "score": 0.007024886877828054} +{"id": "id162", "rank": 323, "score": 0.007010770168664905} +{"id": "id339", "rank": 324, "score": 0.007010770168664905} +{"id": "id163", "rank": 325, "score": 0.006996867746805777} +{"id": "id338", "rank": 326, "score": 0.006996867746805777} +{"id": "id164", "rank": 327, "score": 0.006983177401943145} +{"id": "id337", "rank": 328, "score": 0.006983177401943145} +{"id": "id165", "rank": 329, "score": 0.0069696969696969695} +{"id": "id336", "rank": 330, "score": 0.0069696969696969695} +{"id": "id166", "rank": 331, "score": 0.0069564243306822} +{"id": "id335", "rank": 332, "score": 0.0069564243306822} +{"id": "id167", "rank": 333, "score": 0.006943357409602183} +{"id": "id334", "rank": 334, "score": 0.006943357409602183} +{"id": "id168", "rank": 335, "score": 0.006930494174367216} +{"id": "id333", "rank": 336, "score": 0.006930494174367216} +{"id": "id169", "rank": 337, "score": 0.0069178326352375} +{"id": "id332", "rank": 338, "score": 0.0069178326352375} +{"id": "id170", "rank": 339, "score": 0.00690537084398977} +{"id": "id331", "rank": 340, "score": 0.00690537084398977} +{"id": "id171", "rank": 341, "score": 0.0068931068931068935} +{"id": "id330", "rank": 342, "score": 0.0068931068931068935} +{"id": "id172", "rank": 343, "score": 0.006881038914989805} +{"id": "id329", "rank": 344, "score": 0.006881038914989805} +{"id": "id173", "rank": 345, "score": 0.006869165081191097} +{"id": "id328", "rank": 346, "score": 0.006869165081191097} +{"id": "id174", "rank": 347, "score": 0.006857483601669649} +{"id": "id327", "rank": 348, "score": 0.006857483601669649} +{"id": "id175", "rank": 349, "score": 0.006845992724065704} +{"id": "id326", "rank": 350, "score": 0.006845992724065704} +{"id": "id176", "rank": 351, "score": 0.006834690732995818} +{"id": "id325", "rank": 352, "score": 0.006834690732995818} +{"id": "id177", "rank": 353, "score": 0.0068235759493670885} +{"id": "id324", "rank": 354, "score": 0.0068235759493670885} +{"id": "id178", "rank": 355, "score": 0.006812646729710161} +{"id": "id323", "rank": 356, "score": 0.006812646729710161} +{"id": "id179", "rank": 357, "score": 0.0068019014655304605} +{"id": "id322", "rank": 358, "score": 0.0068019014655304605} +{"id": "id180", "rank": 359, "score": 0.006791338582677165} +{"id": "id321", "rank": 360, "score": 0.006791338582677165} +{"id": "id181", "rank": 361, "score": 0.006780956540729417} +{"id": "id320", "rank": 362, "score": 0.006780956540729417} +{"id": "id182", "rank": 363, "score": 0.006770753832399311} +{"id": "id319", "rank": 364, "score": 0.006770753832399311} +{"id": "id183", "rank": 365, "score": 0.006760728982951205} +{"id": "id318", "rank": 366, "score": 0.006760728982951205} +{"id": "id184", "rank": 367, "score": 0.00675088054963691} +{"id": "id317", "rank": 368, "score": 0.00675088054963691} +{"id": "id185", "rank": 369, "score": 0.006741207121146332} +{"id": "id316", "rank": 370, "score": 0.006741207121146332} +{"id": "id186", "rank": 371, "score": 0.0067317073170731715} +{"id": "id315", "rank": 372, "score": 0.0067317073170731715} +{"id": "id187", "rank": 373, "score": 0.006722379787395268} +{"id": "id314", "rank": 374, "score": 0.006722379787395268} +{"id": "id188", "rank": 375, "score": 0.006713223211969212} +{"id": "id313", "rank": 376, "score": 0.006713223211969212} +{"id": "id189", "rank": 377, "score": 0.006704236300038865} +{"id": "id312", "rank": 378, "score": 0.006704236300038865} +{"id": "id190", "rank": 379, "score": 0.006695417789757413} +{"id": "id311", "rank": 380, "score": 0.006695417789757413} +{"id": "id191", "rank": 381, "score": 0.006686766447722623} +{"id": "id310", "rank": 382, "score": 0.006686766447722623} +{"id": "id192", "rank": 383, "score": 0.006678281068524971} +{"id": "id309", "rank": 384, "score": 0.006678281068524971} +{"id": "id193", "rank": 385, "score": 0.0066699604743083} +{"id": "id308", "rank": 386, "score": 0.0066699604743083} +{"id": "id194", "rank": 387, "score": 0.006661803514342724} +{"id": "id307", "rank": 388, "score": 0.006661803514342724} +{"id": "id195", "rank": 389, "score": 0.00665380906460945} +{"id": "id306", "rank": 390, "score": 0.00665380906460945} +{"id": "id196", "rank": 391, "score": 0.00664597602739726} +{"id": "id305", "rank": 392, "score": 0.00664597602739726} +{"id": "id197", "rank": 393, "score": 0.0066383033309103355} +{"id": "id304", "rank": 394, "score": 0.0066383033309103355} +{"id": "id198", "rank": 395, "score": 0.0066307899288871805} +{"id": "id303", "rank": 396, "score": 0.0066307899288871805} +{"id": "id199", "rank": 397, "score": 0.00662343480023038} +{"id": "id302", "rank": 398, "score": 0.00662343480023038} +{"id": "id200", "rank": 399, "score": 0.006616236948646921} +{"id": "id301", "rank": 400, "score": 0.006616236948646921} +{"id": "id201", "rank": 401, "score": 0.0066091954022988505} +{"id": "id300", "rank": 402, "score": 0.0066091954022988505} +{"id": "id202", "rank": 403, "score": 0.006602309213464033} +{"id": "id299", "rank": 404, "score": 0.006602309213464033} +{"id": "id203", "rank": 405, "score": 0.006595577458206768} +{"id": "id298", "rank": 406, "score": 0.006595577458206768} +{"id": "id204", "rank": 407, "score": 0.00658899923605806} +{"id": "id297", "rank": 408, "score": 0.00658899923605806} +{"id": "id205", "rank": 409, "score": 0.006582573669705321} +{"id": "id296", "rank": 410, "score": 0.006582573669705321} +{"id": "id206", "rank": 411, "score": 0.006576299904691305} +{"id": "id295", "rank": 412, "score": 0.006576299904691305} +{"id": "id207", "rank": 413, "score": 0.0065701771091220725} +{"id": "id294", "rank": 414, "score": 0.0065701771091220725} +{"id": "id208", "rank": 415, "score": 0.006564204473383789} +{"id": "id293", "rank": 416, "score": 0.006564204473383789} +{"id": "id209", "rank": 417, "score": 0.006558381209868199} +{"id": "id292", "rank": 418, "score": 0.006558381209868199} +{"id": "id210", "rank": 419, "score": 0.0065527065527065526} +{"id": "id291", "rank": 420, "score": 0.0065527065527065526} +{"id": "id211", "rank": 421, "score": 0.006547179757511861} +{"id": "id290", "rank": 422, "score": 0.006547179757511861} +{"id": "id212", "rank": 423, "score": 0.006541800101129277} +{"id": "id289", "rank": 424, "score": 0.006541800101129277} +{"id": "id213", "rank": 425, "score": 0.0065365668813944676} +{"id": "id288", "rank": 426, "score": 0.0065365668813944676} +{"id": "id214", "rank": 427, "score": 0.006531479416899808} +{"id": "id287", "rank": 428, "score": 0.006531479416899808} +{"id": "id215", "rank": 429, "score": 0.00652653704676826} +{"id": "id286", "rank": 430, "score": 0.00652653704676826} +{"id": "id216", "rank": 431, "score": 0.006521739130434783} +{"id": "id285", "rank": 432, "score": 0.006521739130434783} +{"id": "id217", "rank": 433, "score": 0.006517085047435144} +{"id": "id284", "rank": 434, "score": 0.006517085047435144} +{"id": "id218", "rank": 435, "score": 0.006512574197202005} +{"id": "id283", "rank": 436, "score": 0.006512574197202005} +{"id": "id219", "rank": 437, "score": 0.006508205998868138} +{"id": "id282", "rank": 438, "score": 0.006508205998868138} +{"id": "id220", "rank": 439, "score": 0.006503979891076666} +{"id": "id281", "rank": 440, "score": 0.006503979891076666} +{"id": "id221", "rank": 441, "score": 0.0064998953317981995} +{"id": "id280", "rank": 442, "score": 0.0064998953317981995} +{"id": "id222", "rank": 443, "score": 0.006495951798154773} +{"id": "id279", "rank": 444, "score": 0.006495951798154773} +{"id": "id223", "rank": 445, "score": 0.006492148786250445} +{"id": "id278", "rank": 446, "score": 0.006492148786250445} +{"id": "id224", "rank": 447, "score": 0.006488485811008485} +{"id": "id277", "rank": 448, "score": 0.006488485811008485} +{"id": "id225", "rank": 449, "score": 0.006484962406015038} +{"id": "id276", "rank": 450, "score": 0.006484962406015038} +{"id": "id226", "rank": 451, "score": 0.006481578123369168} +{"id": "id275", "rank": 452, "score": 0.006481578123369168} +{"id": "id227", "rank": 453, "score": 0.006478332533539194} +{"id": "id274", "rank": 454, "score": 0.006478332533539194} +{"id": "id228", "rank": 455, "score": 0.006475225225225225} +{"id": "id273", "rank": 456, "score": 0.006475225225225225} +{"id": "id229", "rank": 457, "score": 0.006472255805227832} +{"id": "id272", "rank": 458, "score": 0.006472255805227832} +{"id": "id230", "rank": 459, "score": 0.006469423898322742} +{"id": "id271", "rank": 460, "score": 0.006469423898322742} +{"id": "id231", "rank": 461, "score": 0.006466729147141519} +{"id": "id270", "rank": 462, "score": 0.006466729147141519} +{"id": "id232", "rank": 463, "score": 0.006464171212058126} +{"id": "id269", "rank": 464, "score": 0.006464171212058126} +{"id": "id233", "rank": 465, "score": 0.006461749771081328} +{"id": "id268", "rank": 466, "score": 0.006461749771081328} +{"id": "id234", "rank": 467, "score": 0.006459464519752855} +{"id": "id267", "rank": 468, "score": 0.006459464519752855} +{"id": "id235", "rank": 469, "score": 0.006457315171051263} +{"id": "id266", "rank": 470, "score": 0.006457315171051263} +{"id": "id236", "rank": 471, "score": 0.0064553014553014555} +{"id": "id265", "rank": 472, "score": 0.0064553014553014555} +{"id": "id237", "rank": 473, "score": 0.006453423120089787} +{"id": "id264", "rank": 474, "score": 0.006453423120089787} +{"id": "id238", "rank": 475, "score": 0.006451679930184719} +{"id": "id263", "rank": 476, "score": 0.006451679930184719} +{"id": "id239", "rank": 477, "score": 0.006450071667462972} +{"id": "id262", "rank": 478, "score": 0.006450071667462972} +{"id": "id240", "rank": 479, "score": 0.006448598130841122} +{"id": "id261", "rank": 480, "score": 0.006448598130841122} +{"id": "id241", "rank": 481, "score": 0.006447259136212625} +{"id": "id260", "rank": 482, "score": 0.006447259136212625} +{"id": "id242", "rank": 483, "score": 0.00644605451639021} +{"id": "id259", "rank": 484, "score": 0.00644605451639021} +{"id": "id243", "rank": 485, "score": 0.006444984121053615} +{"id": "id258", "rank": 486, "score": 0.006444984121053615} +{"id": "id244", "rank": 487, "score": 0.006444047816702639} +{"id": "id257", "rank": 488, "score": 0.006444047816702639} +{"id": "id245", "rank": 489, "score": 0.006443245486615481} +{"id": "id256", "rank": 490, "score": 0.006443245486615481} +{"id": "id246", "rank": 491, "score": 0.006442577030812325} +{"id": "id255", "rank": 492, "score": 0.006442577030812325} +{"id": "id247", "rank": 493, "score": 0.006442042366024192} +{"id": "id254", "rank": 494, "score": 0.006442042366024192} +{"id": "id248", "rank": 495, "score": 0.006441641425666985} +{"id": "id253", "rank": 496, "score": 0.006441641425666985} +{"id": "id249", "rank": 497, "score": 0.006441374159820762} +{"id": "id252", "rank": 498, "score": 0.006441374159820762} +{"id": "id250", "rank": 499, "score": 0.00644124053521419} +{"id": "id251", "rank": 500, "score": 0.00644124053521419} diff --git a/parity/golden/rrf/interleaved.ndjson b/parity/golden/rrf/interleaved.ndjson new file mode 100644 index 0000000..005faa0 --- /dev/null +++ b/parity/golden/rrf/interleaved.ndjson @@ -0,0 +1,5 @@ +{"id": "id5", "rank": 1, "score": 0.03177805800756621} +{"id": "id1", "rank": 2, "score": 0.03177805800756621} +{"id": "id2", "rank": 3, "score": 0.031754032258064516} +{"id": "id4", "rank": 4, "score": 0.031754032258064516} +{"id": "id3", "rank": 5, "score": 0.031746031746031744} diff --git a/parity/golden/rrf/one-channel.ndjson b/parity/golden/rrf/one-channel.ndjson new file mode 100644 index 0000000..b732bf6 --- /dev/null +++ b/parity/golden/rrf/one-channel.ndjson @@ -0,0 +1,20 @@ +{"id": "id1", "rank": 1, "score": 0.01639344262295082} +{"id": "id2", "rank": 2, "score": 0.016129032258064516} +{"id": "id3", "rank": 3, "score": 0.015873015873015872} +{"id": "id4", "rank": 4, "score": 0.015625} +{"id": "id5", "rank": 5, "score": 0.015384615384615385} +{"id": "id6", "rank": 6, "score": 0.015151515151515152} +{"id": "id7", "rank": 7, "score": 0.014925373134328358} +{"id": "id8", "rank": 8, "score": 0.014705882352941176} +{"id": "id9", "rank": 9, "score": 0.014492753623188406} +{"id": "id10", "rank": 10, "score": 0.014285714285714285} +{"id": "id11", "rank": 11, "score": 0.014084507042253521} +{"id": "id12", "rank": 12, "score": 0.013888888888888888} +{"id": "id13", "rank": 13, "score": 0.0136986301369863} +{"id": "id14", "rank": 14, "score": 0.013513513513513514} +{"id": "id15", "rank": 15, "score": 0.013333333333333334} +{"id": "id16", "rank": 16, "score": 0.013157894736842105} +{"id": "id17", "rank": 17, "score": 0.012987012987012988} +{"id": "id18", "rank": 18, "score": 0.01282051282051282} +{"id": "id19", "rank": 19, "score": 0.012658227848101266} +{"id": "id20", "rank": 20, "score": 0.0125} diff --git a/parity/golden/rrf/repeat.ndjson b/parity/golden/rrf/repeat.ndjson new file mode 100644 index 0000000..2c0ab64 --- /dev/null +++ b/parity/golden/rrf/repeat.ndjson @@ -0,0 +1,2 @@ +{"id": "a", "rank": 1, "score": 0.01639344262295082} +{"id": "b", "rank": 2, "score": 0.016129032258064516} diff --git a/parity/golden/rrf/single.ndjson b/parity/golden/rrf/single.ndjson new file mode 100644 index 0000000..b5a790a --- /dev/null +++ b/parity/golden/rrf/single.ndjson @@ -0,0 +1 @@ +{"id": "only", "rank": 1, "score": 0.02459016393442623} diff --git a/parity/golden/rrf/three-channels.ndjson b/parity/golden/rrf/three-channels.ndjson new file mode 100644 index 0000000..559040a --- /dev/null +++ b/parity/golden/rrf/three-channels.ndjson @@ -0,0 +1,11 @@ +{"id": "a", "rank": 1, "score": 0.05671602326811211} +{"id": "y", "rank": 2, "score": 0.04021516393442623} +{"id": "c", "rank": 3, "score": 0.032266458495966696} +{"id": "b", "rank": 4, "score": 0.0315136476426799} +{"id": "x", "rank": 5, "score": 0.015873015873015872} +{"id": "d", "rank": 6, "score": 0.015625} +{"id": "e", "rank": 7, "score": 0.015384615384615385} +{"id": "f", "rank": 8, "score": 0.015151515151515152} +{"id": "z", "rank": 9, "score": 0.015151515151515152} +{"id": "g", "rank": 10, "score": 0.014925373134328358} +{"id": "h", "rank": 11, "score": 0.014705882352941176} diff --git a/parity/oracle/rrf.py b/parity/oracle/rrf.py new file mode 100644 index 0000000..1da854f --- /dev/null +++ b/parity/oracle/rrf.py @@ -0,0 +1,53 @@ +"""Fuse ranked lists the way the retrieval layer fuses them. + +The pipeline's fusion is written inline in a database query function, so the +scoring loop is spelled out again here — but the two constants that decide +everything are imported from it, so a change to either shows up as a parity +failure rather than as a silently different ranking. + +Reads hit records on stdin and writes `{"id", "rank", "score"}` per line. + + CATENA_PARITY_PYTHONPATH=/path/to/knowledge-mcp/src \ + python3 oracle/rrf.py < hits.ndjson +""" + +import json +import os +import sys + +for path in os.environ.get("CATENA_PARITY_PYTHONPATH", "").split(os.pathsep): + if path: + sys.path.insert(0, path) + +from knowledge_mcp.retrieval import META_WEIGHT, RRF_K # noqa: E402 + +WEIGHTS = {"vec": 1.0, "fts": 1.0, "meta": META_WEIGHT} + + +def main() -> int: + pool: dict[str, dict] = {} + for line in sys.stdin.buffer.read().decode("utf-8").split("\n"): + if not line.strip(): + continue + hit = json.loads(line) + entry = pool.setdefault(hit["id"], {"score": 0.0, "seen": {}}) + channel = hit["channel"] + weight = WEIGHTS.get(channel, 1.0) + previous = entry["seen"].get(channel) + if previous is not None: + entry["score"] -= weight / (RRF_K + previous) + entry["seen"][channel] = hit["rank"] + entry["score"] += weight / (RRF_K + hit["rank"]) + + # Python's dict keeps insertion order and its sort is stable, so ties come + # out in the order the ids were first seen. + ranked = sorted(pool.items(), key=lambda kv: kv[1]["score"], reverse=True) + + out = sys.stdout + for rank, (hit_id, entry) in enumerate(ranked, 1): + out.write(json.dumps({"id": hit_id, "rank": rank, "score": entry["score"]}) + "\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/parity/src/bin/regen.rs b/parity/src/bin/regen.rs index d231183..486de1c 100644 --- a/parity/src/bin/regen.rs +++ b/parity/src/bin/regen.rs @@ -19,7 +19,7 @@ type Written = Result, String>; type Job = (&'static str, fn() -> Written); fn main() -> ExitCode { - let jobs: [Job; 7] = [ + let jobs: [Job; 8] = [ ("tokens", tokens), ("unescape", unescape), ("chunkmd", chunkmd), @@ -27,6 +27,7 @@ fn main() -> ExitCode { ("pdftriage", pdftriage), ("cuenorm", cuenorm), ("cueseg", cueseg), + ("rrf", rrf), ]; for (name, job) in jobs { @@ -70,6 +71,21 @@ fn unescape() -> Written { Ok(vec![path.display().to_string()]) } +fn rrf() -> Written { + let dir = parity::golden("rrf"); + std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; + + let mut written = Vec::new(); + for case in parity::cases_with_extension("rrf", "ndjson") { + let hits = std::fs::read_to_string(&case).map_err(|e| e.to_string())?; + let fused = parity::run_oracle("rrf.py", &hits)?; + let path = dir.join(format!("{}.ndjson", parity::case_name(&case))); + std::fs::write(&path, fused).map_err(|e| e.to_string())?; + written.push(path.display().to_string()); + } + Ok(written) +} + fn cueseg() -> Written { let dir = parity::golden("cueseg"); std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; diff --git a/parity/tests/rrf.rs b/parity/tests/rrf.rs new file mode 100644 index 0000000..36b03c1 --- /dev/null +++ b/parity/tests/rrf.rs @@ -0,0 +1,77 @@ +//! Fused ranks and scores must match the retrieval layer exactly. +//! +//! A fused score is a sum of reciprocals, so the order it produces depends on +//! the arithmetic being done the same way and on ties being broken the same +//! way. Neither is visible in a result list — it just comes out in a slightly +//! different order than the system it is meant to replace. + +use corpus_core::Hit; +use serde_json::Value; + +fn hits(case: &std::path::Path) -> Vec { + let text = std::fs::read_to_string(case).expect("a readable case"); + corpus_core::ndjson::read(text.as_bytes()) + .collect::>() + .expect("valid hit records") +} + +#[test] +fn every_case_fuses_the_same_as_the_retrieval_layer() { + let cases = parity::cases_with_extension("rrf", "ndjson"); + assert!(!cases.is_empty(), "no cases found"); + + for case in cases { + let name = parity::case_name(&case); + let golden = parity::read_golden_text(&format!("rrf/{name}.ndjson")); + let theirs: Vec = golden + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| serde_json::from_str(line).expect("valid JSON")) + .collect(); + let ours = rrf::fuse(&hits(&case), &rrf::Weights::default()); + + for (at, (ours, theirs)) in ours.iter().zip(&theirs).enumerate() { + assert_eq!( + ours.id, + theirs["id"].as_str().expect("an id"), + "{name}: position {at} holds a different hit" + ); + assert_eq!( + ours.rank as u64, + theirs["rank"].as_u64().expect("a rank"), + "{name}: {} ranks differently", + ours.id + ); + assert_eq!( + ours.score, + theirs["score"].as_f64().expect("a score"), + "{name}: {} scores differently", + ours.id + ); + } + assert_eq!( + ours.len(), + theirs.len(), + "{name}: python fused {} hits, rust {}", + theirs.len(), + ours.len() + ); + } +} + +/// Catches a golden file drifting away from a retrieval layer that has moved +/// on — in particular a change to the rank constant or the metadata weight. +#[test] +#[cfg(feature = "python")] +fn the_golden_files_still_match_live_python() { + for case in parity::cases_with_extension("rrf", "ndjson") { + let name = parity::case_name(&case); + let input = std::fs::read_to_string(&case).expect("a readable case"); + let live = parity::run_oracle("rrf.py", &input).expect("the oracle runs"); + assert_eq!( + live, + parity::read_golden_text(&format!("rrf/{name}.ndjson")), + "golden file for {name} is stale" + ); + } +}