Skip to content

fix(search): rank the word as typed above its stem variants - #237

Merged
ben-dev-au merged 1 commit into
mainfrom
fix/exact-form-boost
Nov 1, 2026
Merged

ben-dev-au merged 1 commit into
mainfrom
fix/exact-form-boost

Conversation

@ben-dev-au

Copy link
Copy Markdown
Owner

Problem

Searching collect, collect() or "collect()" ranked chunks full of collections above chunks holding collect itself. Stemming folds both to one stem, so nothing credited the form typed.

Change

  • Split weight: a plain body word, or a one-word phrase, now reads half its weight (EXACT_FORM_SHARE) from the word as typed (F_WORDS) and half from its stem.
  • Only order moves: the stem query stays required, so match sets, counts and highlights are unchanged.
  • Per word, inside the compiler (_Compiler._exact_form):
    • A pattern, fuzzy or field leaf beside a plain word keeps its whole weight.
    • ^N scales both halves.
    • A negated word earns nothing.
  • Multi-word phrases score as before. F_WORDS stores no positions, so a phrase's words counted apart would outscore the phrase itself.
  • Shared, not added. Adding the typed form on top of the stems cost NQ deeper-section MRR up to 0.051. Sharing the weight costs about a tenth of that.
  • No schema change, no reindex.

Measured against main

Public benchmarks, paired randomisation p:

Set Result
NQ page nDCG@10 +0.004 full questions (p 0.02), +0.004 3-word (p 0.02), +0.007 2-word (p < 0.001)
NQ full questions, within-page MRR, deeper answers -0.006 (p 0.03 to 0.05 across scorings)
SciFact, NFCorpus, CoSQA level, no cell at p < 0.05
Strong-signal shortcut NFCorpus 16 to 12 of 323; never fires on NQ, SciFact or CoSQA either way
Latency within 1 ms

The deeper-answer MRR cell is the one result below main at p < 0.05: one of about 40 compared, and it would not survive a multiple-comparison correction. It is in the metric the split was designed to protect, so it is stated here rather than left to the code comment.

Tests

tests/test_exact_form_outranks_stem_variants.py, with negative controls run for every case:

  • Ranking (5): the word as typed outranks its variants for collect, collect(), "collect()" and COLLECT, and collections outranks collected. All fail with the share at 0, which is main's behaviour.
  • Guards from the adversarial review: pattern weight, ^N, field scope (title:"foo bar" OR baz), a colon that names no field (TODO:"fix it"), a negated phrase, and phrase ranking. Each fails against the earlier designs, or a mutant, that had the defect.
  • Full suite passes; the query-syntax matrix against main shows 0 changed.

Stemming gave the form typed no credit, so collect, collect() and "collect()" ranked chunks full of collections above chunks holding collect itself.

A plain body word or one-word phrase now reads half its weight from the word as typed (F_WORDS) and half from its stem. Its stem query stays required, so match sets, counts and highlights are unchanged; only order moves.

- The split happens per word inside the compiler, so a pattern, fuzzy or field leaf beside a plain word keeps its whole weight, ^N scales both halves, and a negated word earns nothing.
- Multi-word phrases are scored as before: F_WORDS has no positions, and the phrase's words counted apart would outscore the phrase.
- Adding the typed form on top of the stems instead cost NQ deeper-section MRR up to 0.051, so the weight is shared, not added.

Measured against main (public benchmarks, paired randomisation p):
- NQ page nDCG@10: +0.004 full questions (p 0.02), +0.004 3-word (p 0.02), +0.007 2-word (p < 0.001).
- NQ full questions, within-page MRR for deeper answers: -0.006 (p 0.03 to 0.05 across scorings). The one cell below main at p < 0.05.
- SciFact, NFCorpus, CoSQA: level (no cell at p < 0.05).
- Strong-signal shortcut: NFCorpus 16 to 12 of 323 queries; never fires on NQ, SciFact or CoSQA either way.
- Latency within 1 ms; no schema change, no reindex.
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4503be39-3013-43a2-8104-2aa4247f0d3d
Commits

Reviewing files that changed from the base of the PR and between 4eedd85 and 92f192f.

Files selected for processing: 6
  • README.md
  • fnd/analysis.py
  • fnd/query.py
  • fnd/query_compile.py
  • fnd/schema.py
  • tests/test_exact_form_outranks_stem_variants.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Plain body words now split their query weight between the stemmed query and the typed form. Regression tests cover ranking and query behaviour, and the README updates its invoice search example.

Changes: Body word ranking

Layer / File(s) Summary
Exact-form query compilation
fnd/schema.py, fnd/analysis.py, fnd/query_compile.py
The compiler accepts an exact-form share. For a single token, it weights the stemmed query by the remaining share and adds the typed word as a Should clause. The body-word share is set to 0.5.
Body query wiring and validation
fnd/query.py, tests/test_exact_form_outranks_stem_variants.py, README.md
The searcher passes the configured share to body-query compilation. Regression tests cover ranking, field scoping, patterns, boosts, and phrase behaviour. The README states that invoice outweighs invoices.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: Minimal · up to 92f19

Exact forms receive additional ranking weight. No actionable merge-blocking risk was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title names the main change: search ranking now credits the word as typed over its stem variants. It matches the code changes in fnd/query_compile.py, fnd/schema.py and fnd/analysis.py. The title …
Description check Passed The description explains the problem (stemming folds collect and collections together, so the typed form earns no credit). It describes the split-weight change, the EXACT_FORM_SHARE and F_WORDS usage,…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: search ranking now credits the word as typed over its stem variants. It matches the code changes in fnd/query_compile.py, fnd/schema.py and fnd/analysis.py. The title uses the fix(search) convention and is specific. It is 59 characters, which is within a normal title length.

Full details: Description check

Explanation

The description explains the problem (stemming folds collect and collections together, so the typed form earns no credit). It describes the split-weight change, the EXACT_FORM_SHARE and F_WORDS usage, the per-word rules in _Compiler._exact_form, and the new test file. It also reports benchmark results against main, including one below-main metric. The description is directly related to the changeset.

Full details: Docstring Coverage

Explanation

Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 12 functions across 5 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

I’m a rabbit with a query to run,
Stem and typed word share the fun.
Half the weight goes to the form,
Half keeps stemming as the norm.
Tests check the order, clear and bright,
Then I hop off into the night.

@ben-dev-au
ben-dev-au merged commit 59ee8b4 into main Nov 1, 2026
22 checks passed
@ben-dev-au
ben-dev-au deleted the fix/exact-form-boost branch November 1, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant