57161fb762
Nothing at read time told a feed item or a crawled page apart from his own notes. QueryNotes ranked every note by cosine and the notes answer handed the nearest five to the phraser, so "что я говорил про переезд" could be answered out of a stranger's web page, prefixed with "вот что я нашла: ". Recall now excludes the read sources, rss: and crawl:, and the list is one place. The feed answer needed a different read as a result, and it needed one anyway: it scanned the last 200 notes of any source, so a busy day of voice notes pushed the newest headline out of the window and she said "в лентах пока ничего нового" while the poller was working fine. RecentNotesFromSource asks for feed notes by source, so the window holds 200 of them. Found in review of #66 and #67.
79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestQueryNotesRanksByCosine(t *testing.T) {
|
|
ctx := context.Background()
|
|
st := newTestStore(t)
|
|
|
|
now := time.Now()
|
|
// 3-dim vectors along distinct axes; query aligns with the "backups" note.
|
|
if _, err := st.WriteNote(ctx, now, "prefer backups at 3am", []float32{1, 0, 0}, "tap:voice"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.WriteNote(ctx, now, "gpu driver fixed the flicker", []float32{0, 1, 0}, "tap:voice"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.WriteNote(ctx, now, "cat likes the window", []float32{0, 0, 1}, "tap:voice"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := st.QueryNotes(ctx, []float32{0.9, 0.1, 0}, 2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("want 2 notes, got %d", len(got))
|
|
}
|
|
if got[0].Text != "prefer backups at 3am" {
|
|
t.Errorf("nearest = %q, want backups note (score %.3f)", got[0].Text, got[0].Score)
|
|
}
|
|
if got[0].Score <= got[1].Score {
|
|
t.Errorf("scores not descending: %.3f then %.3f", got[0].Score, got[1].Score)
|
|
}
|
|
}
|
|
|
|
// Recall answers questions about HIM. A feed item and a crawled page are text
|
|
// Maven read somewhere, and letting them into the nearest-neighbour pool means
|
|
// "что я говорил про переезд" can be answered with a stranger's sentence, under
|
|
// the line "вот что я нашла: ".
|
|
func TestQueryNotesLeavesOutWhatSheOnlyRead(t *testing.T) {
|
|
ctx := context.Background()
|
|
st := newTestStore(t)
|
|
now := time.Now()
|
|
// The read sources sit exactly on the query vector; his own note is further
|
|
// away, so ranking alone would put them first.
|
|
for _, n := range []struct{ text, source string }{
|
|
{"переезд в новую квартиру описан тут", "rss:habr"},
|
|
{"страница про переезд", "crawl:changelog"},
|
|
} {
|
|
if _, err := st.WriteNote(ctx, now, n.text, []float32{1, 0, 0}, n.source); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := st.WriteNote(ctx, now, "переезд в субботу", []float32{0.8, 0.6, 0}, "tap:voice"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := st.QueryNotes(ctx, []float32{1, 0, 0}, 5)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 || got[0].Source != "tap:voice" {
|
|
t.Fatalf("recall returned %+v; want only what he said himself", got)
|
|
}
|
|
|
|
// They are still reachable, by source.
|
|
feed, err := st.RecentNotesFromSource(ctx, "rss:", 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(feed) != 1 || feed[0].Source != "rss:habr" {
|
|
t.Fatalf("RecentNotesFromSource(rss:) = %+v; want the one feed note", feed)
|
|
}
|
|
}
|