one weekday matcher, and a stem list stops answering for sredstvo (V-581)
router.WeekdayIndex reads the lexicon and asks the dictionary about the case. Four private lists go away: the habit declension map, the weekday block of the day-plan refusal, the weekday and part-of-day entries of the weather guard, and the stem list in ruwords.go. The stem list was the real defect. mentionsUnknownDay matched sred, pyatnits and subbot with strings.Contains, so sredi, sredstvo and sredniy all read as Wednesday and a question carrying one was answered with onlyNearDaysReply instead of a date. It matches whole tokens now. The weather guard was a third copy of three closed sets that already exist. It kept the rooms of the house, which are its own, and asks the lexicon for the weekdays, the parts of the day and the words that follow v without naming a place. Questions phrased v srede, v utra and v obshchem reached the geocoder as cities before. Full suite green under -race.
This commit is contained in:
+14
-9
@@ -17,6 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kami/maven/internal/lexicon"
|
||||
"github.com/kami/maven/internal/router"
|
||||
"github.com/kami/maven/internal/say"
|
||||
)
|
||||
|
||||
@@ -73,22 +74,26 @@ func mentionsUnknownPlace(u string) bool {
|
||||
// date for a day she did not understand.
|
||||
const onlyNearDaysReply = "я считаю только сегодня, завтра, послезавтра и вчера — про другие дни пока не скажу."
|
||||
|
||||
// dayWords — day references the calendar parser cannot resolve. A weekday name
|
||||
// or a "через …" phrase means he asked about a specific other day.
|
||||
var dayWords = []string{
|
||||
"понедельник", "вторник", "сред", "четверг", "пятниц", "суббот", "воскресен",
|
||||
"через", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
|
||||
}
|
||||
|
||||
// mentionsUnknownDay reports whether the question names a day the calendar
|
||||
// parser could not resolve. Mirror of mentionsUnknownPlace: it exists only to
|
||||
// pick an honest reply over a confidently wrong one.
|
||||
//
|
||||
// Only called after ParseCalendarDate has already failed, so "завтра" and the
|
||||
// other words it does know never reach here.
|
||||
//
|
||||
// The weekday half was a list of STEMS matched with strings.Contains until
|
||||
// V-581 — "сред", "пятниц", "суббот". That is the hand-written Russian pattern
|
||||
// the sweep of 2026-08-04 took out, and it was wrong in the way such a pattern
|
||||
// always is: "среди", "средство" and "средний" all contain "сред", so a question
|
||||
// carrying any of them was answered with onlyNearDaysReply instead of the date.
|
||||
// Whole tokens now, and the weekday itself is router.WeekdayIndex, which reads
|
||||
// the lexicon and asks the dictionary about the case.
|
||||
func mentionsUnknownDay(u string) bool {
|
||||
for _, w := range dayWords {
|
||||
if strings.Contains(u, w) {
|
||||
for _, tok := range quietTokens(u) {
|
||||
if tok == "через" {
|
||||
return true
|
||||
}
|
||||
if _, ok := router.WeekdayIndex(tok); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestMentionsUnknownDayReadsWordsNotStems — the defect V-581 found. The
|
||||
// weekday half of this guard was a list of stems matched with strings.Contains,
|
||||
// so "среди", "средство" and "средний" all read as Wednesday and the question
|
||||
// was answered with onlyNearDaysReply instead of a date.
|
||||
//
|
||||
// The other half of the fix is coverage: a stem list stops at the forms whoever
|
||||
// wrote it thought of, and "воскресеньях" was not one of them.
|
||||
func TestMentionsUnknownDayReadsWordsNotStems(t *testing.T) {
|
||||
for _, u := range []string{
|
||||
"какое число в понедельник",
|
||||
"какое число в среду",
|
||||
"какое число в среде",
|
||||
"что там по воскресеньям",
|
||||
"what is the date on friday",
|
||||
"какое число через неделю",
|
||||
} {
|
||||
if !mentionsUnknownDay(u) {
|
||||
t.Errorf("mentionsUnknownDay(%q) = false, want true", u)
|
||||
}
|
||||
}
|
||||
for _, u := range []string{
|
||||
"какое число в среднем",
|
||||
"сколько это в среднем",
|
||||
"какое сегодня средство",
|
||||
"какое число",
|
||||
} {
|
||||
if mentionsUnknownDay(u) {
|
||||
t.Errorf("mentionsUnknownDay(%q) = true; it names no day", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
-9
@@ -7,6 +7,10 @@ package main
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/kami/maven/internal/lexicon"
|
||||
"github.com/kami/maven/internal/morph"
|
||||
"github.com/kami/maven/internal/router"
|
||||
)
|
||||
|
||||
// isWeatherQuery returns true if the utterance is about weather.
|
||||
@@ -27,16 +31,42 @@ func isWeatherQuery(u string) bool {
|
||||
// come through whole and "в 5 утра" does not.
|
||||
var weatherPlace = regexp.MustCompile(`(?i)(?:^|\s)(?:в|во|in)\s+([\p{L}-]+(?:\s+[\p{L}-]+)?)`)
|
||||
|
||||
// weatherNonPlaces — words that follow "в" in a weather question and are not
|
||||
// cities. "какая погода в доме" is the smart-home sensor, not Open-Meteo, and
|
||||
// "тепло в комнате" is the same question about the same room.
|
||||
var weatherNonPlaces = map[string]bool{
|
||||
// weatherRooms — the rooms of the house, which are the only words in this
|
||||
// guard that belong to it. "какая погода в доме" is the smart-home sensor, not
|
||||
// Open-Meteo, and "тепло в комнате" is the same question about the same room.
|
||||
//
|
||||
// The rest of the guard used to be a third copy of three closed sets that
|
||||
// already exist in the lexicon: the weekdays, the parts of the day, and the
|
||||
// words that follow "в" without naming a place (V-581). Each copy was short in
|
||||
// its own direction — "среду" but not "среде", "утром" but not "утра", "целом"
|
||||
// but not "общем" — so the same question phrased one word differently reached
|
||||
// the geocoder as a city.
|
||||
var weatherRooms = map[string]bool{
|
||||
"доме": true, "квартире": true, "комнате": true, "спальне": true,
|
||||
"гостиной": true, "кухне": true, "гараже": true, "офисе": true,
|
||||
"выходные": true, "субботу": true, "воскресенье": true, "понедельник": true,
|
||||
"вторник": true, "среду": true, "четверг": true, "пятницу": true,
|
||||
"обед": true, "обеде": true, "утро": true, "утром": true, "вечер": true,
|
||||
"вечером": true, "ночь": true, "ночью": true, "целом": true, "принципе": true,
|
||||
"обед": true, "обеде": true, "выходные": true, "выходных": true,
|
||||
}
|
||||
|
||||
// isWeatherNonPlace reports whether the word after "в" names something other
|
||||
// than a place he could ask the weather for.
|
||||
func isWeatherNonPlace(word string) bool {
|
||||
if weatherRooms[word] {
|
||||
return true
|
||||
}
|
||||
if _, ok := router.WeekdayIndex(word); ok {
|
||||
return true
|
||||
}
|
||||
for _, w := range lexicon.PartsOfDay() {
|
||||
if word == w || morph.SameWord(word, w) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, w := range lexicon.NotPlaceAfterV() {
|
||||
if word == w {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// extractWeatherLocation returns the place he named, or the configured default
|
||||
@@ -63,7 +93,7 @@ func extractWeatherLocation(u, defaultLoc string) string {
|
||||
}
|
||||
place := strings.TrimSpace(m[1])
|
||||
first := strings.ToLower(strings.Fields(place)[0])
|
||||
if weatherNonPlaces[first] {
|
||||
if isWeatherNonPlace(first) {
|
||||
return defaultLoc
|
||||
}
|
||||
return place
|
||||
|
||||
@@ -29,6 +29,13 @@ func TestExtractWeatherLocation(t *testing.T) {
|
||||
// the house sensors and the day words answer elsewhere.
|
||||
{"тепло в комнате?", "Berlin", "Berlin"},
|
||||
{"какая погода в выходные", "Berlin", "Berlin"},
|
||||
// The cases the three private copies of the lexicon were short by
|
||||
// (V-581): a weekday in a case the old map did not list, a part of the
|
||||
// day in one it did not list, and "в общем".
|
||||
{"какая погода в среде", "Berlin", "Berlin"},
|
||||
{"какая погода в воскресеньях", "Berlin", "Berlin"},
|
||||
{"какая погода в понедельникам", "Berlin", "Berlin"},
|
||||
{"какая погода в общем", "Berlin", "Berlin"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := extractWeatherLocation(c.utterance, c.def); got != c.want {
|
||||
|
||||
Reference in New Issue
Block a user