The search string to score titles against.
The titles to score, from collectCachedTitles.
Scorer subset, supplier filter, minScore cutoff, and row limit.
Per-scorer rankings, a merged title-by-scorer matrix, and the titles the scorers disagree on most.
const result = scoreCorpus("sodium chloride", corpus, { limit: 2 });
result.byScorer.ratio; // => [{ title: "Sodium Chloride", score: 100, ... }, ...]
result.spread[0]; // => { title: "NaCl 500g", min: 8, max: 90, spread: 82, avg: 41 }
export function scoreCorpus(
query: string,
corpus: CorpusEntry[],
options: FuzzProbeOptions = {},
): FuzzProbeResult {
const scorers = options.scorers ?? [...FUZZ_SCORER_NAMES];
const limit = resolveLimit(options.limit);
const minScore = options.minScore ?? 0;
const filtered = filterBySuppliers(corpus, options.suppliers);
const byTitle: FuzzProbeResult['byTitle'] = [];
for (const entry of filtered) {
const scores: Record<string, number> = {};
for (const name of scorers) {
scores[name] = FUZZ_SCORERS[name](query, entry.title);
}
const values = Object.values(scores);
if (values.length > 0 && Math.max(...values) < minScore) {
continue;
}
byTitle.push({ ...entry, scores });
}
const byScorer: Record<string, ScoredEntry[]> = {};
for (const name of scorers) {
byScorer[name] = byTitle
.map(({ scores, ...entry }) => ({ ...entry, score: scores[name] }))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
const spread = byTitle
.map((row) => {
const values = Object.values(row.scores);
const min = Math.min(...values);
const max = Math.max(...values);
return {
title: row.title,
min,
max,
spread: max - min,
avg: Math.round(values.reduce((sum, value) => sum + value, 0) / values.length),
};
})
.sort((a, b) => b.spread - a.spread);
return { query, corpusSize: filtered.length, scorers, byScorer, byTitle, spread };
}
Scores every corpus title with every active fuzz scorer, so the scorers can be compared side by side against real product titles. Pure — performs no IO and prints nothing; fuzzTest is the console-facing wrapper.