diff --git a/internal/router/calendar.go b/internal/router/calendar.go index 3f86976..4fd7870 100644 --- a/internal/router/calendar.go +++ b/internal/router/calendar.go @@ -29,13 +29,26 @@ var dayPlanWords = []string{ "plan", "plans", "schedule", "agenda", } -// otherDayWords — a day that is not today. The plan is built for the clock's -// own day only, so an utterance naming another one belongs to the calendar -// listing instead. Claiming it here would answer the wrong day, which is worse -// than answering more tersely. +// otherDayWords — a span that is not the clock's own day. The plan can only be +// built for today, so an utterance naming another day, a weekday, a week or a +// weekend belongs to the calendar listing instead. Claiming it here would +// answer today and stamp it with today's date, which is a wrong answer where +// falling through is only a terse one. +// +// The weekday names are here as a refusal, not as a feature. "какие планы на +// понедельник?" carries no other-day token in the сегодня family and does carry +// "планы", so the plan used to claim it and recite today. var otherDayWords = []string{ "завтра", "послезавтра", "вчера", "позавчера", "tomorrow", "yesterday", + "понедельник", "вторник", "среду", "среда", "четверг", "пятницу", "пятница", + "субботу", "суббота", "воскресенье", + "понедельника", "вторника", "четверга", "пятницы", "субботы", "воскресенья", + "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", + "неделю", "неделя", "недели", "неделе", + "выходные", "выходных", "выходным", + "месяц", "месяца", "месяце", + "week", "weekend", "month", } // IsDayPlanQuery reports whether an utterance asks for today's plan (Vikunja @@ -67,6 +80,18 @@ func IsDayPlanQuery(text string) bool { (hasTok(toks, "what") && hasTok(toks, "next")) } +// IsRestOfDayQuery reports whether the utterance asks for what is left of the +// day rather than for the whole of it — "что дальше?" and its English form. +// +// Tokenized for the same reason IsDayPlanQuery is: the substring form matched +// "дальше" inside longer words and "next" inside "nextcloud", and the two +// predicates deciding the same utterance differently is worse than either +// being wrong on its own. +func IsRestOfDayQuery(text string) bool { + toks := planTokens(text) + return hasTok(toks, "дальше") || hasTok(toks, "next") +} + func hasTok(toks []string, w string) bool { for _, t := range toks { if t == w { diff --git a/internal/router/calendar_test.go b/internal/router/calendar_test.go index 4772733..7937a94 100644 --- a/internal/router/calendar_test.go +++ b/internal/router/calendar_test.go @@ -81,3 +81,38 @@ func TestIsDayPlanQuery(t *testing.T) { } } } + +// The plan is built for the clock's own day. A weekday, a week or a weekend +// carries no сегодня-family token, so the plan used to claim the utterance and +// recite today under today's date. Refusing is the right answer until the plan +// can build a day that is not the clock's own. +func TestIsDayPlanQueryRefusesOtherSpans(t *testing.T) { + for _, s := range []string{ + "какие планы на понедельник?", + "планы на пятницу", + "какие планы на неделю?", + "планы на выходные", + "какие планы на месяц?", + "what are my plans for friday?", + "my plan for the week", + } { + if IsDayPlanQuery(s) { + t.Errorf("IsDayPlanQuery(%q) = true, want false", s) + } + } +} + +// The rest-of-day test tokenizes like IsDayPlanQuery does. The substring form +// it replaced fired on any word containing "next" or "дальше". +func TestIsRestOfDayQuery(t *testing.T) { + for _, s := range []string{"что дальше?", "и что потом, дальше?", "what's next", "NEXT"} { + if !IsRestOfDayQuery(s) { + t.Errorf("IsRestOfDayQuery(%q) = false, want true", s) + } + } + for _, s := range []string{"какие планы на сегодня?", "проверь nextcloud", "дальшесъезд", ""} { + if IsRestOfDayQuery(s) { + t.Errorf("IsRestOfDayQuery(%q) = true, want false", s) + } + } +}