The raw search string, which may use AND/OR/NOT/parentheses.
The titles to evaluate, from collectCachedTitles.
Scorer, threshold, fuzzyWords, supplier filter, and row limit.
The parsed AST, the ranked survivors, the drops with their failed terms,
and the titles whose survival depends on fuzzyWords.
const result = evaluateCorpusAst("sodium AND NOT borohydride", corpus);
result.astText; // => '(sodium AND NOT borohydride)'
result.matched[0]; // => { title: "Sodium Chloride", score: 86, ... }
result.dropped[0].failedTerms; // => ["sodium"]
export function evaluateCorpusAst(
query: string,
corpus: CorpusEntry[],
options: AstProbeOptions = {},
): AstProbeResult {
const scorerName = options.scorer ?? DEFAULT_AST_SCORER;
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
const fuzzyWords = options.fuzzyWords ?? true;
const filtered = filterBySuppliers(corpus, options.suppliers);
const parsed = parseSearchQuery(query);
const evalOptions = { scorer: FUZZ_SCORERS[scorerName], threshold, fuzzyWords };
const compare = options.compareFuzzyWords !== false;
const inverted = { ...evalOptions, fuzzyWords: !fuzzyWords };
const matched: ScoredEntry[] = [];
const dropped: DroppedEntry[] = [];
const fuzzyWordsDelta: AstProbeResult['fuzzyWordsDelta'] = [];
for (const entry of filtered) {
const score = scoreAstMatch(entry.title, parsed.ast, evalOptions);
if (score === null) {
dropped.push({ ...entry, failedTerms: explainDrop(entry.title, parsed.ast, fuzzyWords) });
} else {
matched.push({ ...entry, score });
}
if (!compare) {
continue;
}
const survivesInverted = scoreAstMatch(entry.title, parsed.ast, inverted) !== null;
if ((score !== null) === survivesInverted) {
continue;
}
// Name the fuzzyWords setting that kept it, whichever pass that was.
const keptByFuzzy = score !== null ? fuzzyWords : !fuzzyWords;
fuzzyWordsDelta.push({ ...entry, onlyWith: keptByFuzzy ? 'fuzzyWords' : 'exact' });
}
matched.sort((a, b) => b.score - a.score);
return {
query,
isAdvanced: parsed.isAdvanced,
ast: parsed.ast,
astText: renderAst(parsed.ast),
corpusSize: filtered.length,
scorer: scorerName,
threshold,
fuzzyWords,
matched,
dropped,
fuzzyWordsDelta,
};
}
Evaluates a corpus against an advanced (boolean) search query using the same
parseSearchQuery→scoreAstMatchpath production suppliers take inSupplierBase.fuzzyFilterAst. Pure — performs no IO and prints nothing.Note the leaf gate is word presence, not similarity, so the chosen
scoreronly ranks survivors;fuzzyWordsandthresholdare what change which titles survive.compareFuzzyWordssurfaces exactly that difference.