list read-back asks the seeds; add and clear keep their tables (V-522)

internal/router/list.go was the last file on the sweep, and the answer is a
split rather than one mechanism. What the four paths need is different, and the
V-529 comment in the file already had half of the argument.

Reading a list back needs one bit — is this about the list — so topicList joins
the subjects in cmd/mavend/topics.go and queryList calls turnIsAbout.
listQueryPrefixes stays as the offline floor. Which list he named is a noun in
the dictionary either way, through the new router.ListNamedIn, which scans the
whole utterance: the seeds claim a read-back without eating a prefix, so "что
мне нужно в аптеке" has nothing for takeListTag to read the front of.

The other three keep their phrase tables, and the header says why. Add and
remove have to know WHERE the item starts, and a cosine over a whole utterance
does not say which byte the milk begins at. Clear deletes the list, so a false
claim loses rows he cannot get back — that is not the trade a margin makes.

Measured on TestONNXTopics, four held-out cases added: 27/27, no case
regressed. Two existing margins moved by under a hundredth because the new
seeds became the runner-up, both still far clear of topicMargin.
This commit is contained in:
2026-08-05 18:43:13 +04:00
parent 3e87ad7eb6
commit a08403087f
4 changed files with 73 additions and 2 deletions
+34
View File
@@ -17,6 +17,15 @@ import (
// The markers are deliberately explicit. "молоко закончилось" is an
// observation about the world and belongs in a note; only an instruction to
// put something on a list puts it there.
//
// The four paths split on 05-08-2026, and the split is by what the caller needs
// rather than by language (V-522). Reading a list back needs one bit — is this
// about the list — so the seeds decide it, topicList through turnIsAbout, and
// listQueryPrefixes below is the offline floor. The other three keep the tables
// as the answer. Add and remove need to know WHERE the item starts, and a
// cosine over a whole utterance does not say which byte the milk begins at.
// Clear DELETES the list, so it stays on exact phrases: a false claim there
// loses rows he cannot get back, which is not the trade a margin makes.
// listTags — the lists he can name, as one dictionary form each. Russian
// declines the tag ("список покупок", "в покупки", "в покупках"), and the
@@ -186,6 +195,31 @@ func ParseListQuery(text string) (string, bool) {
return list, true
}
// ListNamedIn reports which standing list an utterance names, anywhere in it,
// defaulting to покупки when it names none.
//
// takeListTag is not enough for a read-back, because it reads the FRONT of a
// remainder a prefix table has already eaten. The seeds claim a read-back
// without eating anything (topicList, cmd/mavend/topics.go, V-522), so "что мне
// нужно в аптеке" has to be scanned rather than trimmed. A list name is a noun
// in the dictionary, so this is a lookup and decides nothing about meaning.
func ListNamedIn(text string) string {
for _, f := range strings.Fields(strings.ToLower(text)) {
head := strings.Trim(f, listTrimCut)
for _, s := range listTags {
if morph.SameWord(head, s.word) {
return s.list
}
}
for _, s := range listTagsEN {
if head == s.word {
return s.list
}
}
}
return "покупки"
}
// ParseListClear reports whether an utterance crosses off a whole list.
func ParseListClear(text string) (string, bool) {
lower := strings.ToLower(strings.Trim(strings.TrimSpace(text), listTrimCut))