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:
kami
2026-07-06 04:09:47 +04:00
parent 3b8fb691cb
commit cf066bde97
11 changed files with 212 additions and 1 deletions
+26
View File
@@ -79,6 +79,32 @@ func (s *Store) RecentFacts(ctx context.Context, n int) ([]Fact, error) {
return out, rows.Err()
}
// CalendarEvents returns caldav facts whose key date falls within [from, to).
// Calendar event keys have the format calendar_event_YYYYMMDD_<summary>.
func (s *Store) CalendarEvents(ctx context.Context, from, to time.Time) ([]Fact, error) {
prefixFrom := fmt.Sprintf("calendar_event_%s", from.Format("20060102"))
prefixTo := fmt.Sprintf("calendar_event_%s", to.Format("20060102"))
rows, err := s.db.QueryContext(ctx, `
SELECT id, ts, kind, key, value, source, confidence, voids_id
FROM facts
WHERE source = 'poll:caldav'
AND key >= ? AND key < ?
ORDER BY key`, prefixFrom, prefixTo)
if err != nil {
return nil, fmt.Errorf("calendar events: %w", err)
}
defer rows.Close()
var out []Fact
for rows.Next() {
f, err := scanFact(rows)
if err != nil {
return nil, err
}
out = append(out, f)
}
return out, rows.Err()
}
// LatestFactBySource — provenance-scoped. A rule on `service_down` trusts only
// source=poll:healthcheck; a compromised poller can't forge a trigger. Use this
// from rules, not LatestFact, whenever the rule's source contract matters.
+43
View File
@@ -334,4 +334,47 @@ func TestPresenceStateSingletonRoundtrip(t *testing.T) {
if b, score, _, err := s.LoadPresenceState(ctx); err != nil || b != Present || score != 0.83 {
t.Fatalf("after save: want Present/0.83, got %s/%f (%v)", b, score, err)
}
}
func TestCalendarEvents(t *testing.T) {
store := newTestStore(t)
defer store.Close()
ctx := context.Background()
now := time.Date(2026, 7, 6, 12, 0, 0, 0, time.UTC)
// Write facts for different dates
store.WriteFact(ctx, time.Date(2026, 7, 6, 8, 0, 0, 0, time.UTC), KindSelf, "calendar_event_20260706_Morning-standup", `"Morning standup @ 10:00-10:30"`, "poll:caldav", 1.0, sql.NullInt64{})
store.WriteFact(ctx, time.Date(2026, 7, 6, 8, 0, 0, 0, time.UTC), KindSelf, "calendar_event_20260706_Lunch", `"Lunch @ 12:00-13:00"`, "poll:caldav", 1.0, sql.NullInt64{})
store.WriteFact(ctx, time.Date(2026, 7, 6, 8, 0, 0, 0, time.UTC), KindSelf, "calendar_event_20260707_Doctor", `"Doctor @ 14:00-15:00"`, "poll:caldav", 1.0, sql.NullInt64{})
store.WriteFact(ctx, time.Date(2026, 7, 6, 8, 0, 0, 0, time.UTC), KindSelf, "calendar_event_20260705_Yesterday", `"Yesterday thing"`, "poll:caldav", 1.0, sql.NullInt64{})
// Query July 6
events, err := store.CalendarEvents(ctx, now.Truncate(24*time.Hour), now.Truncate(24*time.Hour).Add(24*time.Hour))
if err != nil {
t.Fatalf("CalendarEvents: %v", err)
}
if len(events) != 2 {
t.Fatalf("expected 2 events on July 6, got %d", len(events))
}
// Query July 7
t7 := time.Date(2026, 7, 7, 0, 0, 0, 0, time.UTC)
events, err = store.CalendarEvents(ctx, t7, t7.Add(24*time.Hour))
if err != nil {
t.Fatalf("CalendarEvents: %v", err)
}
if len(events) != 1 {
t.Fatalf("expected 1 event on July 7, got %d", len(events))
}
// Query date with no events
t8 := time.Date(2026, 7, 8, 0, 0, 0, 0, time.UTC)
events, err = store.CalendarEvents(ctx, t8, t8.Add(24*time.Hour))
if err != nil {
t.Fatalf("CalendarEvents: %v", err)
}
if len(events) != 0 {
t.Fatalf("expected 0 events on July 8, got %d", len(events))
}
}