router: let a habit question outrank the day plan, and know the weekend

IsDayPlanQuery fires on the token "планы" and its other-day list does not know
weekday names, so "какие у меня обычно планы по вторникам?" was claimed by the
day plan, which answered today's calendar stamped with today's date. The habit
source never ran. The matcher now declines any utterance ParseHabitQuery
claims, which keeps the decision out of the source table's ordering.

Two gaps in the same matcher. Sunday had only its dative plural listed, so "в
воскресенье" found no weekday. "по выходным" named days that no weekday word
matches, so it was answered with the whole-week profile. Both are recognised
now, and the weekend is read back as two days rather than pooled.

Found in review of #59.
This commit is contained in:
kami
2026-08-01 14:06:05 +04:00
parent ba33a677f8
commit c21d8fdcee
7 changed files with 130 additions and 0 deletions
+9
View File
@@ -45,6 +45,15 @@ var otherDayWords = []string{
// сегодня?" and a plan that hijacks every date-bearing question would bury the
// events under checklist lines. Only a plan-shaped ask, and only about today.
func IsDayPlanQuery(text string) bool {
// A habit question is never a day plan, whatever words it shares with one.
// "какие у меня обычно планы по вторникам?" carries "планы", so the plan
// source claimed it and answered today's calendar stamped with today's
// date, and the habit source never ran. Deciding it here rather than by
// reordering the source table keeps one matcher from depending on the
// other's position in a slice.
if _, ok := ParseHabitQuery(text); ok {
return false
}
toks := planTokens(text)
for _, t := range toks {
for _, w := range otherDayWords {
+15
View File
@@ -11,9 +11,11 @@ import "time"
// HabitQuery — a parsed "what do I usually do" question. Weekday is set only
// when the utterance names one; otherwise the answer covers the whole week.
// Weekend is set for "по выходным", which names two days rather than one.
type HabitQuery struct {
Weekday time.Weekday
HasWeekday bool
Weekend bool
}
// habitMarkers — the words that make a question about habit rather than about
@@ -35,6 +37,8 @@ var weekdayWords = map[string]time.Weekday{
"пятница": time.Friday, "пятницу": time.Friday, "пятницам": time.Friday,
"суббота": time.Saturday, "субботу": time.Saturday, "субботам": time.Saturday,
"воскресенье": time.Sunday, "воскресеньям": time.Sunday,
"воскресенья": time.Sunday, "воскресенью": time.Sunday,
"воскресеньем": time.Sunday, "воскресеньях": time.Sunday,
"monday": time.Monday, "mondays": time.Monday,
"tuesday": time.Tuesday, "tuesdays": time.Tuesday,
"wednesday": time.Wednesday, "wednesdays": time.Wednesday,
@@ -44,6 +48,14 @@ var weekdayWords = map[string]time.Weekday{
"sunday": time.Sunday, "sundays": time.Sunday,
}
// weekendWords — the weekend as one unit. "что я обычно делаю по выходным?"
// has a habit marker and names days, but no weekday name is in it, so it used
// to fall through to the whole-week profile and answer about Tuesdays too.
var weekendWords = map[string]bool{
"выходным": true, "выходные": true, "выходных": true, "выходной": true,
"weekend": true, "weekends": true,
}
// ParseHabitQuery reports whether an utterance asks what the owner usually
// does, and on which weekday if it names one.
//
@@ -68,6 +80,9 @@ func ParseHabitQuery(text string) (HabitQuery, bool) {
if wd, ok := weekdayWords[t]; ok {
return HabitQuery{Weekday: wd, HasWeekday: true}, true
}
if weekendWords[t] {
return HabitQuery{Weekend: true}, true
}
}
return HabitQuery{}, true
}
+45
View File
@@ -43,3 +43,48 @@ func TestParseHabitQuery(t *testing.T) {
}
}
}
// TestHabitQueryBeatsDayPlan — "какие у меня обычно планы по вторникам?" is a
// habit question that happens to carry a plan word. The day plan claimed it
// first and answered today's calendar stamped with today's date, and the habit
// source never ran.
func TestHabitQueryBeatsDayPlan(t *testing.T) {
for _, q := range []string{
"какие у меня обычно планы по вторникам?",
"что обычно по плану в среду?",
"какие планы обычно по выходным?",
} {
if IsDayPlanQuery(q) {
t.Errorf("%q was claimed as a day plan", q)
}
if _, ok := ParseHabitQuery(q); !ok {
t.Errorf("%q is not parsed as a habit question", q)
}
}
// A plan question without a habit marker still belongs to the day plan.
for _, q := range []string{"какие планы на сегодня?", "что у меня по плану?"} {
if !IsDayPlanQuery(q) {
t.Errorf("%q must still be a day plan", q)
}
}
}
// TestHabitQueryWeekendAndSundayForms — "по выходным" names days but no
// weekday, so it used to be answered with the whole-week profile. Sunday had
// only its dative plural listed.
func TestHabitQueryWeekendAndSundayForms(t *testing.T) {
q, ok := ParseHabitQuery("что я обычно делаю по выходным?")
if !ok || !q.Weekend || q.HasWeekday {
t.Errorf("weekend query parsed as %+v (ok=%v)", q, ok)
}
for _, s := range []string{
"что я обычно делаю в воскресенье?",
"чем я обычно занят по воскресеньям?",
"что обычно бывает в воскресенья?",
} {
q, ok := ParseHabitQuery(s)
if !ok || !q.HasWeekday || q.Weekday != time.Sunday {
t.Errorf("%q parsed as %+v (ok=%v)", s, q, ok)
}
}
}