A first backtest showed BM25 summed over a wing's top hits favours big wings (0/4 agreement). Best-page scoring is volume-robust; pinned by a test that failed before the change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
62 lines
2.4 KiB
Go
62 lines
2.4 KiB
Go
package wing_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/wing"
|
|
)
|
|
|
|
func writePage(t *testing.T, dir, rel, body string) {
|
|
t.Helper()
|
|
full := filepath.Join(dir, rel)
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(full), 0o755))
|
|
require.NoError(t, os.WriteFile(full, []byte(body), 0o644))
|
|
}
|
|
|
|
func scorerBrain(t *testing.T) string {
|
|
dir := t.TempDir()
|
|
writePage(t, dir, "wiki/infra/facts/k3s.md", "---\ntitle: K3s cluster\n---\nkubernetes cluster nodes scheduling pods deployment\n")
|
|
writePage(t, dir, "wiki/finance/facts/invoice.md", "---\ntitle: Invoices\n---\ninvoice payment bookkeeping ledger reconciliation\n")
|
|
return dir
|
|
}
|
|
|
|
func TestBM25Scorer_RanksTheWingWhosePagesMatch(t *testing.T) {
|
|
s := wing.NewBM25Scorer(scorerBrain(t), []string{"infra", "finance"}, "")
|
|
got := s.Score("notes about kubernetes cluster scheduling and pods")
|
|
assert.Greater(t, got["infra"], got["finance"])
|
|
assert.Greater(t, got["infra"], 0.0)
|
|
}
|
|
|
|
func TestBM25Scorer_ExcludesThePageBeingClassified(t *testing.T) {
|
|
// A page must not vouch for its own wing when it is the backtest sample.
|
|
s := wing.NewBM25Scorer(scorerBrain(t), []string{"infra", "finance"}, "wiki/infra/facts/k3s.md")
|
|
got := s.Score("kubernetes cluster nodes scheduling pods deployment")
|
|
assert.Zero(t, got["infra"], "the excluded page is the only infra match")
|
|
}
|
|
|
|
func TestBM25Scorer_NoUsableTermsScoresNothing(t *testing.T) {
|
|
s := wing.NewBM25Scorer(scorerBrain(t), []string{"infra", "finance"}, "")
|
|
assert.Empty(t, s.Score("a an to of"))
|
|
}
|
|
|
|
// A big wing must not win just by having more pages that brush the query: a
|
|
// wing is scored by its single best page, not by the sum of its top hits.
|
|
func TestBM25Scorer_ABigWingDoesNotWinOnVolume(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writePage(t, dir, "wiki/small/facts/target.md", "---\ntitle: Target\n---\nkubernetes cluster scheduling pods deployment nodes\n")
|
|
for i := 0; i < 8; i++ {
|
|
writePage(t, dir, filepath.Join("wiki/big/facts", "p"+string(rune('a'+i))+".md"),
|
|
"---\ntitle: Filler\n---\nkubernetes cluster scheduling mentioned in passing among bookkeeping ledger entries\n")
|
|
}
|
|
s := wing.NewBM25Scorer(dir, []string{"small", "big"}, "")
|
|
got := s.Score("kubernetes cluster scheduling pods deployment nodes")
|
|
top, ok := wing.TopWing(got)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, "small", top)
|
|
}
|