ed9bdd5e09
The plan answers "какие планы на сегодня?" by putting one day in order: calendar events (with #126's ambient provenance carried through and hedged), pending reminders, and one line per morning routine that still has items outstanding. "что дальше?" trims what has already passed. It lives in internal/morning, not in a parallel system, because it is the same question the checklist asks at a different scale — the routine knows what is missing from a window, the plan knows what the whole day holds, and both read the same facts and the same idea of "today". BuildPlan is pure; tickLoop.dayPlan is the impure half that reads the store. It is not a nag. Nothing here fires, schedules or announces: the plan is built only when asked, over IPC (day_plan) or on the existing /morning page. Unprompted delivery stays with the morning nudge and the dispatcher's policy. The query source sits before "calendar" in querySources because both match "…на сегодня" and the plan's matcher is the more specific one; IsDayPlanQuery matches whole words so "планёрка" (a meeting) is not read as a request for the plan, and refuses any utterance naming another day, since the plan is built for the clock's own day only. Verified: make build and make test both exit 0; new tests cover plan ordering, the checklist-only-what-is-left rule, other-day rejection, the RU rendering against the persona checks, rest-of-day trimming, the source ordering, and the matcher's refusals.
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package router
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCalendarEventFormatter(t *testing.T) {
|
|
f := CalendarEventFormatter{}
|
|
date := time.Date(2026, 7, 6, 0, 0, 0, 0, time.UTC)
|
|
|
|
// Empty
|
|
if got := f.Format(nil, date); got != "на 06.07.2026 ничего нет." {
|
|
t.Errorf("empty: got %q", got)
|
|
}
|
|
|
|
// One event
|
|
if got := f.Format([]string{"Standup @ 10:00-10:30"}, date); got != "на 06.07.2026: Standup @ 10:00-10:30" {
|
|
t.Errorf("one: got %q", got)
|
|
}
|
|
|
|
// Multiple events
|
|
if got := f.Format([]string{"Standup @ 10:00-10:30", "Lunch @ 12:00-13:00"}, date); got != "на 06.07.2026: Standup @ 10:00-10:30; Lunch @ 12:00-13:00" {
|
|
t.Errorf("multiple: got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestCalendarEventFormatterHedgesUncertainEntries(t *testing.T) {
|
|
f := CalendarEventFormatter{}
|
|
date := time.Date(2026, 7, 6, 0, 0, 0, 0, time.UTC)
|
|
|
|
// An event relayed off a phone notification is not a calendar read, and she
|
|
// says so instead of reciting a guess as fact.
|
|
got := f.FormatEntries([]CalendarEntry{
|
|
{Text: "Standup @ 10:00-10:30"},
|
|
{Text: "Планёрка @ 14:00-14:30", Uncertain: true},
|
|
}, date)
|
|
want := "на 06.07.2026: Standup @ 10:00-10:30; похоже, Планёрка @ 14:00-14:30"
|
|
if got != want {
|
|
t.Errorf("got %q\nwant %q", got, want)
|
|
}
|
|
|
|
// Format is FormatEntries with everything certain.
|
|
if got := f.FormatEntries(nil, date); got != "на 06.07.2026 ничего нет." {
|
|
t.Errorf("empty: got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestIsDayPlanQuery(t *testing.T) {
|
|
yes := []string{
|
|
"какие планы на сегодня?",
|
|
"что у меня по плану",
|
|
"расскажи план",
|
|
"мой распорядок на сегодня",
|
|
"расписание?",
|
|
"что дальше?",
|
|
"what's next",
|
|
"what is my plan today",
|
|
}
|
|
for _, s := range yes {
|
|
if !IsDayPlanQuery(s) {
|
|
t.Errorf("IsDayPlanQuery(%q) = false, want true", s)
|
|
}
|
|
}
|
|
|
|
no := []string{
|
|
// The calendar listing owns these.
|
|
"что у меня сегодня?",
|
|
"какие планы на завтра?",
|
|
"план на послезавтра",
|
|
"что было вчера",
|
|
// "планёрка" is a meeting, not a request for the plan.
|
|
"когда планёрка?",
|
|
"запиши планёрку на 14:00",
|
|
"какая погода?",
|
|
"",
|
|
}
|
|
for _, s := range no {
|
|
if IsDayPlanQuery(s) {
|
|
t.Errorf("IsDayPlanQuery(%q) = true, want false", s)
|
|
}
|
|
}
|
|
}
|