maven: calendar event querying (task 3)
Store: CalendarEvents(ctx, from, to) — filters caldav facts by key date prefix. IPC: full wiring through interface, server dispatch, and client. Router: ParseCalendarDate (сегодня/завтра), CalendarEventFormatter (RU reply). Voice: calendar detection before notes RAG in IntentQuery handler. Tests: store integration test, date parser tests, formatter tests. Co-Authored-By: opencode <opencode@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CalendarEventFormatter formats calendar events into a Russian reply string.
|
||||
type CalendarEventFormatter struct{}
|
||||
|
||||
// Format returns a Russian reply for the given calendar events on the given date.
|
||||
func (CalendarEventFormatter) Format(events []string, date time.Time) string {
|
||||
dateStr := date.Format("02.01.2006")
|
||||
if len(events) == 0 {
|
||||
return fmt.Sprintf("на %s ничего нет.", dateStr)
|
||||
}
|
||||
return fmt.Sprintf("на %s: %s", dateStr, strings.Join(events, "; "))
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -327,3 +327,17 @@ func parseDurationValue(s string) (string, bool) {
|
||||
}
|
||||
return strconv.Itoa(n) + unit, true
|
||||
}
|
||||
|
||||
// ParseCalendarDate detects RU calendar date words in text and returns the
|
||||
// resolved time (midnight UTC+0 for "сегодня"/"today", next day for "завтра"/"tomorrow").
|
||||
// Returns zero time + false if no match.
|
||||
func ParseCalendarDate(text string, now time.Time) (time.Time, bool) {
|
||||
lower := strings.ToLower(text)
|
||||
if strings.Contains(lower, "сегодня") || strings.Contains(lower, "today") {
|
||||
return now.Truncate(24 * time.Hour), true
|
||||
}
|
||||
if strings.Contains(lower, "завтра") || strings.Contains(lower, "tomorrow") {
|
||||
return now.Truncate(24 * time.Hour).Add(24 * time.Hour), true
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package router
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Russian capture must produce the same keys the loop's care rules read, or a
|
||||
// ru voice test silently writes nothing and the nudges look broken.
|
||||
@@ -28,3 +31,28 @@ func TestDefaultFactParserRU(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCalendarDate(t *testing.T) {
|
||||
now := time.Date(2026, 7, 6, 14, 30, 0, 0, time.UTC)
|
||||
cases := []struct {
|
||||
text string
|
||||
want time.Time
|
||||
ok bool
|
||||
}{
|
||||
{"что у меня сегодня по плану", time.Date(2026, 7, 6, 0, 0, 0, 0, time.UTC), true},
|
||||
{"что завтра", time.Date(2026, 7, 7, 0, 0, 0, 0, time.UTC), true},
|
||||
{"планы на сегодня", time.Date(2026, 7, 6, 0, 0, 0, 0, time.UTC), true},
|
||||
{"расписание на завтра", time.Date(2026, 7, 7, 0, 0, 0, 0, time.UTC), true},
|
||||
{"what's today", time.Date(2026, 7, 6, 0, 0, 0, 0, time.UTC), true},
|
||||
{"tomorrow plans", time.Date(2026, 7, 7, 0, 0, 0, 0, time.UTC), true},
|
||||
{"какая погода", time.Time{}, false},
|
||||
{"сколько времени", time.Time{}, false},
|
||||
{"", time.Time{}, false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got, ok := ParseCalendarDate(c.text, now)
|
||||
if ok != c.ok || !got.Equal(c.want) {
|
||||
t.Errorf("ParseCalendarDate(%q) = %v, %v; want %v, %v", c.text, got, ok, c.want, c.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user