8015fdbb79
Replace nearest-neighbour personal routing with a frozen class-balanced linear head measured on historical, stratified, cross-validation, holdout, and fresh challenge gates (V-702). Close the four repair handoff holes, preserve nested clarification flows, and route Russian possession statements through structural grammar rather than lexical exceptions (V-573). Owner explicitly requested direct commits to master.
113 lines
3.5 KiB
Go
113 lines
3.5 KiB
Go
package router
|
||
|
||
import (
|
||
"strings"
|
||
"unicode"
|
||
|
||
"github.com/kami/maven/internal/lexicon"
|
||
)
|
||
|
||
var possessionQuestionWords = func() map[string]bool {
|
||
words := make(map[string]bool)
|
||
for _, word := range append(lexicon.Interrogatives(), lexicon.NarrativeRequests()...) {
|
||
words[word] = true
|
||
}
|
||
return words
|
||
}()
|
||
|
||
// PossessionStatementGrammar recognises the Russian possessive construction
|
||
// “у меня …” as a statement to remember. Russian has no present-tense “have”:
|
||
// the preposition and genitive pronoun are the grammatical predicate, so a
|
||
// sentence such as “у меня новый ноутбук” contains no verb for a generic
|
||
// sentence parser to anchor on. Both the hash floor and the deployed routing
|
||
// heads have measured this exact shape as a query and would try to answer it
|
||
// instead of recording it.
|
||
//
|
||
// This is a token grammar, not a phrase regexp or a noun list. The two-token
|
||
// frame is a closed grammatical construction and the remainder stays open.
|
||
// Questions and explicit capture/reminder/narrative requests retain their
|
||
// narrower routes; only a plain declaration is claimed.
|
||
func PossessionStatementGrammar() Grammar {
|
||
return Grammar{Name: "possession-statement", Decide: possessionStatementDecision}
|
||
}
|
||
|
||
func possessionStatementDecision(utterance string) (Decision, bool) {
|
||
tokens := planTokens(utterance)
|
||
if len(tokens) < 3 || tokens[0] != "у" || tokens[1] != "меня" {
|
||
return Decision{}, false
|
||
}
|
||
if possessionQuestionShaped(utterance) || CarriesCaptureVerb(utterance) || carriesReminderVerbTokens(tokens) {
|
||
return Decision{}, false
|
||
}
|
||
return Decision{
|
||
Stage: 0,
|
||
Intent: IntentNote,
|
||
Confidence: 1,
|
||
Slots: Slots{Text: utterance},
|
||
}, true
|
||
}
|
||
|
||
// possessionQuestionShaped is the question half of this grammar over Russian
|
||
// word structure. IsQuestionShaped intentionally tokenises punctuation away,
|
||
// which makes the interrogative root in indefinite pronouns look like a
|
||
// question: “что-то сломалось”, “кто-нибудь пришёл”, “когда-то работало”. Here
|
||
// that would defeat the open possession statement this grammar exists for.
|
||
//
|
||
// Hyphenated indefinite forms are a closed grammatical construction, not a
|
||
// phrase list: interrogative+{то, либо, нибудь}, or кое+interrogative. Every
|
||
// other exact interrogative/narrative token remains a question, as does a
|
||
// question mark. No noun or payload vocabulary is involved.
|
||
func possessionQuestionShaped(text string) bool {
|
||
if strings.HasSuffix(strings.TrimSpace(text), "?") {
|
||
return true
|
||
}
|
||
for _, lexeme := range possessionLexemes(text) {
|
||
if possessionQuestionWords[lexeme] {
|
||
return true
|
||
}
|
||
parts := strings.Split(lexeme, "-")
|
||
if indefiniteQuestionCompound(parts) {
|
||
continue
|
||
}
|
||
for _, part := range parts {
|
||
if possessionQuestionWords[part] {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func possessionLexemes(text string) []string {
|
||
return strings.FieldsFunc(strings.ToLower(text), func(r rune) bool {
|
||
return !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '-'
|
||
})
|
||
}
|
||
|
||
func indefiniteQuestionCompound(parts []string) bool {
|
||
if len(parts) != 2 {
|
||
return false
|
||
}
|
||
if parts[0] == "кое" && possessionQuestionWords[parts[1]] {
|
||
return true
|
||
}
|
||
if !possessionQuestionWords[parts[0]] {
|
||
return false
|
||
}
|
||
switch parts[1] {
|
||
case "то", "либо", "нибудь":
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func carriesReminderVerbTokens(tokens []string) bool {
|
||
for _, verb := range lexicon.ReminderVerbs() {
|
||
if hasTok(tokens, verb) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|